// 《围棋》作者版权所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package runtime

import (
	"internal/abi"
	"internal/cpu"
	"internal/goarch"
	"runtime/internal/atomic"
	"runtime/internal/sys"
	"unsafe"
)

// 使用cmd/go/internal/modload进行设置。modinfo prog 
var modinfo string

// Goroutine调度程序
// 调度程序的任务是在工作线程上分发准备运行的Goroutine。
// 
// 主要概念是：
// G-goroutine。
// M-工作线程或机器。
// P-处理器，执行Go代码所需的资源。
// M必须有一个关联的P才能执行Go代码，但是它可以被
// 阻止，或者在没有关联P的系统调用中。
// 
// 位于https:

// 工作线程停放/解压。
// 我们需要在保持足够的运行工作线程以利用
// 可用的硬件并行性和停止过多的运行工作线程之间取得平衡
// 以节省CPU资源和电源。这并不简单，原因有二：
// （1）计划程序状态是故意分布的（尤其是per-P工作
// /队列），因此不可能在快速路径上计算全局谓词；
// （2）为了实现最佳的线程管理，我们需要了解未来（在不久的将来准备好新的goroutine时，不要将
// 作为工作线程）。
// 
// /三种被拒绝的方法效果不佳：
// 1。集中所有调度程序状态（将抑制可伸缩性）。
// 2。直接goroutine切换。也就是说，当我们准备好一个新的goroutine时，
// 是一个空闲的P，解压一个线程，并将其传递给线程和goroutine。
// 这将导致线程状态混乱，因为准备
// goroutine的线程可能在下一刻就无法工作，我们需要将其停驻。
// 同样，它会破坏计算的局部性，因为我们想在同一个线程上保留
// 依赖的goroutine；并引入额外的延迟。
// 3。每当我们准备一个goroutine，并且有一个
// idle P，但不要进行切换时，就再解压一个线程。这将导致过多的线程停驻/
// 解列，因为额外的线程将立即停驻，而不会发现任何要做的工作。
// 
// 当前方法：
// 
// 此方法适用于潜在工作的三个主要来源：准备
// goroutine、新的/修改的早期计时器和空闲优先级GC。
// 更多详细信息，请参见下文。
// 
// 当我们提交工作时，如果（这是wakep（））：
// 1，我们会取消一个额外的线程。有一个空闲的P和
// 2。没有“旋转”的工作线程。
// 
// 如果工作线程没有本地工作，并且没有在全局运行队列或netpoller中找到工作，则认为它正在旋转；旋转状态为
// 用m.旋转和sched表示。旋转。以这种方式断开的线程是
// 也被认为是旋转的；我们不做goroutine切换，所以这样的线程一开始是无法工作的。旋转线程在per-P 
// 运行队列和计时器堆或在停车前从GC中寻找工作时旋转。如果一个正在旋转的
// 线程找到了工作，它会使自己脱离旋转状态并继续执行
// 执行。如果找不到工作，它就会离开旋转的
// /州，然后停下来。
// 
// 如果至少有一个旋转线程（sched.nmspinning>1），我们不会在提交工作时取消新线程的连接。为了补偿这一点，如果最后一个正在旋转的
// 线程找到工作并停止旋转，它必须解绑一个新的正在旋转的
// 线程。这种方法平滑了线程不匹配的不合理峰值，但同时保证了最终的最大CPU并行率。
// 
// 主要的实现复杂度是，在纺纱->非纺纱螺纹过渡期间，我们需要非常小心。此转换可以与提交新工作的
// 竞争，并且一个或另一个部分需要解压
// 另一个工作线程。如果他们都不能做到这一点，我们最终可能会导致
// 半持久性CPU利用率不足。
// 
// 提交的一般模式是：
//  1. 将工作提交到本地运行队列、计时器堆或GC状态。
// 2#StoreLoad风格的记忆屏障。
// 3。查查时间表。旋转。
// 
// 旋转->非旋转转换的一般模式是：
// 1。减少旋转。
// 2#StoreLoad风格的记忆屏障。
// 3。检查所有per-P工作队列和GC是否有新工作。
// 
// 请注意，所有这些复杂性并不适用于全局运行队列，因为我们是
// 在提交到全局队列时，不会草率地进行线程解析。另请参见
// 注释了解更多信息。
// 
// 这些不同的工作源的行为是如何变化的，尽管这并不影响
// 同步方法：
// /*Ready goroutine:这是一个明显的工作源；goroutine是
// 立即准备就绪，最终必须在某个线程上运行。
// /*新的/修改的早期计时器：当前的计时器实现（请参阅time.go）
// 在线程中使用netpoll，没有工作可以等待最快的
// 计时器。如果没有线程等待，我们需要一个新的旋转线程去
// 等待。
// /*空闲优先级GC：GC唤醒一个停止的空闲线程，为
// 后台GC工作做出贡献（注意：当前已根据golang.org/issue/19112禁用）。
// 另见golang。org/issue/44313，因为这应该扩展到所有GC 
// 工人。

var (
	m0           m
	g0           g
	mcache0      *mcache
	raceprocctx0 uintptr
)

// go:linkname runtime_inittask runtime。。inittask 
var runtime_inittask initTask

// go:linkname main_inittask main。。inittask 
var main_inittask initTask

// main_init_done是cgocallbackg使用的一个信号，表示初始化
// 已完成。它是在_cgo_notify_runtime_init_done，
// 之前完成的，因此所有cgo调用都可以依赖它。当main_init完成时，
// 它将关闭，这意味着cgocallbackg可以可靠地从中接收信息。
var main_init_done chan bool

// go:linkname main_main。主
func main_main()

// mainStarted表示主M已启动。
var mainStarted bool

// runtimeInitTime是运行时启动的nanotime（）。
var runtimeInitTime int64

// 用于新创建的M的信号掩码的值。
var initSigmask sigset

// 主goroutine。
func main() {
	g := getg()

	// m0->g0的Racectx仅用作主goroutine的父级。
	// 不能用于其他任何用途。
	g.m.g0.racectx = 0

	// 64位的最大堆栈大小为1GB，32位的最大堆栈大小为250MB。
	// 使用十进制而不是二进制GB和MB，因为
	// 它们在堆栈溢出失败消息中看起来更好。
	if goarch.PtrSize == 8 {
		maxstacksize = 1000000000
	} else {
		maxstacksize = 250000000
	}

	// 最大堆栈大小的上限。用于避免随机崩溃
	// 调用SetMaxStack并尝试分配太大的堆栈后，
	// 因为stackalloc使用32位大小。
	maxstackceiling = 2 * maxstacksize

	// 允许newproc启动新的Ms.
	mainStarted = true

	if GOARCH != "wasm" { // 在wasm上还没有线程，因此没有系统元
		systemstack(func() {
			newm(sysmon, nil, -1)
		})
	}

	// 在初始化期间将主goroutine锁定到主OS线程
	// 上。大多数程序都不在乎，但有几个
	// 确实需要主线程进行某些调用。
	// 这些可以安排主菜单。main通过调用runtime在主线程
	// 中运行。初始化期间使用LockOSThread 
	// 来保留锁。
	lockOSThread()

	if g.m != &m0 {
		throw("runtime.main not on m0")
	}

	// 记录世界何时开始。
	// 必须在doInit之前才能跟踪init。
	runtimeInitTime = nanotime()
	if runtimeInitTime == 0 {
		throw("nanotime returning zero")
	}

	if debug.inittrace != 0 {
		inittrace.id = getg().goid
		inittrace.active = true
	}

	doInit(&runtime_inittask) // 必须在延迟之前。

	// 延迟解锁，以便运行时。init期间的Goexit也会解锁。
	needUnlock := true
	defer func() {
		if needUnlock {
			unlockOSThread()
		}
	}()

	gcenable()

	main_init_done = make(chan bool)
	if iscgo {
		if _cgo_thread_start == nil {
			throw("_cgo_thread_start missing")
		}
		if GOOS != "windows" {
			if _cgo_setenv == nil {
				throw("_cgo_setenv missing")
			}
			if _cgo_unsetenv == nil {
				throw("_cgo_unsetenv missing")
			}
		}
		if _cgo_notify_runtime_init_done == nil {
			throw("_cgo_notify_runtime_init_done missing")
		}
		// 启动模板线程，以防我们从
		// 输入Go，这是一个C创建的线程，需要创建一个新线程。
		startTemplateThread()
		cgocall(_cgo_notify_runtime_init_done, nil)
	}

	doInit(&main_inittask)

	// main init完成后禁用init跟踪，以避免在malloc和newproc中收集统计信息的开销
	// 
	inittrace.active = false

	close(main_init_done)

	needUnlock = false
	unlockOSThread()

	if isarchive || islibrary {
		// 。
		return
	}
	fn := main_main // 进行间接调用，因为在设置运行时链接器不知道主程序包的地址
	fn()
	if raceenabled {
		racefini()
	}

	// 使racy客户端程序工作：如果在
	// 主程序返回的同时出现另一个goroutine，
	// 让另一个goroutine完成打印恐慌跟踪。
	// 一旦它这样做了，它就会退出。见第3934期和第20018期。
	if atomic.Load(&runningPanicDefers) != 0 {
		// 运行延迟函数不会花费很长时间。
		for c := 0; c < 1000; c++ {
			if atomic.Load(&runningPanicDefers) == 0 {
				break
			}
			Gosched()
		}
	}
	if atomic.Load(&panicking) != 0 {
		gopark(nil, nil, waitReasonPanicWait, traceEvGoStop, 1)
	}

	exit(0)
	for {
		var x *int32
		*x = 0
	}
}

// os_beforeExit从os调用。退出（0）。
// go:linkname os_before exit os。运行时_before退出
func os_beforeExit() {
	if raceenabled {
		racefini()
	}
}

// 启动forcegc助手goroutine 
func init() {
	go forcegchelper()
}

func forcegchelper() {
	forcegc.g = getg()
	lockInit(&forcegc.lock, lockRankForcegc)
	for {
		lock(&forcegc.lock)
		if forcegc.idle != 0 {
			throw("forcegc: phase error")
		}
		atomic.Store(&forcegc.idle, 1)
		goparkunlock(&forcegc.lock, waitReasonForceGCIdle, traceEvGoBlock, 1)
		// 此goroutine由sysmon 
		if debug.gctrace > 0 {
			println("GC forced")
		}
		// 时间触发，完全并发。
		gcStart(gcTrigger{kind: gcTriggerTime, now: nanotime()})
	}
}

// go:nosplit 

// Gosched生成处理器，允许其他goroutine运行。它不会
// 挂起当前goroutine，因此执行会自动恢复。
func Gosched() {
	checkTimeouts()
	mcall(gosched_m)
}

// goschedguarded产生与gosched类似的处理器，但也会检查
// 是否存在禁止状态，并在这些情况下选择退出产量。
// go:nosplit 
func goschedguarded() {
	mcall(goschedguarded_m)
}

// 将当前goroutine置于等待状态，并在
// 系统堆栈上调用unlockf。
// 
// 如果unlockf返回false，则goroutine恢复。
// 
// unlockf不能访问此G的堆栈，因为它可能会在
// 对gopark的调用和对unlockf的调用之间移动。
// 
// 请注意，由于unlockf是在G进入等待
// 状态后调用的，因此在unlockf被称为
// 时，G可能已经准备就绪，除非有外部同步阻止G被
// 准备就绪。如果unlockf返回false，它必须保证G不能被
// 外部准备好。
// 
// 原因解释了goroutine停车的原因。它显示在堆栈
// 跟踪和堆转储中。原因应该是独特的和描述性的。不要重复使用理由，添加新的理由。
func gopark(unlockf func(*g, unsafe.Pointer) bool, lock unsafe.Pointer, reason waitReason, traceEv byte, traceskip int) {
	if reason != waitReasonSleep {
		checkTimeouts() // 超时可能会过期，而两个Goroutine会让调度程序忙碌
	}
	mp := acquirem()
	gp := mp.curg
	status := readgstatus(gp)
	if status != _Grunning && status != _Gscanrunning {
		throw("gopark: bad g status")
	}
	mp.waitlock = lock
	mp.waitunlockf = unlockf
	gp.waitreason = reason
	mp.waittraceev = traceEv
	mp.waittraceskip = traceskip
	releasem(mp)
	// 无法执行任何可能在Ms之间移动G的操作。
	mcall(park_m)
}

// 将当前goroutine置于等待状态并解锁。
// 可以通过调用goready（gp）使goroutine再次可运行。
func goparkunlock(lock *mutex, reason waitReason, traceEv byte, traceskip int) {
	gopark(parkunlock_c, unsafe.Pointer(lock), reason, traceEv, traceskip)
}

func goready(gp *g, traceskip int) {
	systemstack(func() {
		ready(gp, traceskip, true)
	})
}

// go:nosplit 
func acquireSudog() *sudog {
	// 微妙的舞蹈：信号量实现调用
	// acquireSudog，acquireSudog调用new（sudog），
	// new调用malloc，malloc可以调用垃圾收集器，
	// ，垃圾收集器在stopTheWorld中调用信号量实现
	// 。
	// 围绕new（sudog）执行acquirem/releasem，打破这个循环。
	// acquirem/releasem在new（sudog）期间增加m.locks，
	// 这会阻止垃圾收集器被调用。
	mp := acquirem()
	pp := mp.p.ptr()
	if len(pp.sudogcache) == 0 {
		lock(&sched.sudoglock)
		// 首先，尝试从中央缓存中获取批处理。
		for len(pp.sudogcache) < cap(pp.sudogcache)/2 && sched.sudogcache != nil {
			s := sched.sudogcache
			sched.sudogcache = s.next
			s.next = nil
			pp.sudogcache = append(pp.sudogcache, s)
		}
		unlock(&sched.sudoglock)
		// 如果中央缓存为空，请分配一个新的。
		if len(pp.sudogcache) == 0 {
			pp.sudogcache = append(pp.sudogcache, new(sudog))
		}
	}
	n := len(pp.sudogcache)
	s := pp.sudogcache[n-1]
	pp.sudogcache[n-1] = nil
	pp.sudogcache = pp.sudogcache[:n-1]
	if s.elem != nil {
		throw("acquireSudog: found s.elem != nil in cache")
	}
	releasem(mp)
	return s
}

// go:nosplit 
func releaseSudog(s *sudog) {
	if s.elem != nil {
		throw("runtime: sudog with non-nil elem")
	}
	if s.isSelect {
		throw("runtime: sudog with non-false isSelect")
	}
	if s.next != nil {
		throw("runtime: sudog with non-nil next")
	}
	if s.prev != nil {
		throw("runtime: sudog with non-nil prev")
	}
	if s.waitlink != nil {
		throw("runtime: sudog with non-nil waitlink")
	}
	if s.c != nil {
		throw("runtime: sudog with non-nil c")
	}
	gp := getg()
	if gp.param != nil {
		throw("runtime: releaseSudog with non-nil gp.param")
	}
	mp := acquirem() // 避免重新调度到另一个P 
	pp := mp.p.ptr()
	if len(pp.sudogcache) == cap(pp.sudogcache) {
		// 将本地缓存的一半传输到中央缓存。
		var first, last *sudog
		for len(pp.sudogcache) > cap(pp.sudogcache)/2 {
			n := len(pp.sudogcache)
			p := pp.sudogcache[n-1]
			pp.sudogcache[n-1] = nil
			pp.sudogcache = pp.sudogcache[:n-1]
			if first == nil {
				first = p
			} else {
				last.next = p
			}
			last = p
		}
		lock(&sched.sudoglock)
		last.next = sched.sudogcache
		sched.sudogcache = first
		unlock(&sched.sudoglock)
	}
	pp.sudogcache = append(pp.sudogcache, s)
	releasem(mp)
}

// 从程序集中调用
func badmcall(fn func(*g)) {
	throw("runtime: mcall called on m->g0 stack")
}

func badmcall2(fn func(*g)) {
	throw("runtime: mcall function returned")
}

func badreflectcall() {
	panic(plainError("arg size to reflect.call more than 1GB"))
}

var badmorestackg0Msg = "fatal: morestack on g0\n"

// go:nosplit 
// go:nowritebarrierrec 
func badmorestackg0() {
	sp := stringStructOf(&badmorestackg0Msg)
	write(2, sp.str, int32(sp.len))
}

var badmorestackgsignalMsg = "fatal: morestack on gsignal\n"

// go:nosplit 
// go:nowritebarrierrec 
func badmorestackgsignal() {
	sp := stringStructOf(&badmorestackgsignalMsg)
	write(2, sp.str, int32(sp.len))
}

// go:nosplit 
func badctxt() {
	throw("ctxt != 0")
}

func lockedOSThread() bool {
	gp := getg()
	return gp.lockedm != 0 && gp.m.lockedg != 0
}

var (
	// 所有Gs包含所有已创建的Gs（包括死Gs），因此
	// 永不收缩。
	// 
	// 通过切片的访问受allglock或stop the world保护。
	// 无法取锁的读者可能会（小心！）使用下面的原子
	// 变量。
	allglock mutex
	allgs    []*g

	// allglen和allgptr是分别包含len（allgs）和
	// &allgs[0]的原子变量。正确的订购取决于完全订购的
	// 装载和存储。写操作受allglock保护。
	// 
	// allgptr在allglen之前更新。读者应在allgptr之前阅读allglen 
	// 以确保allglen始终<=len（allgptr）。比赛中添加的新
	// Gs可能会被错过。对于
	// 所有Gs的一致观点，必须持有allglock。
	// 
	// 所有GPTR副本应始终存储为具体类型或
	// 不安全。指针，而不是uintptr，以确保GC仍然可以到达它
	// 即使它指向一个过时的数组。
	allglen uintptr
	allgptr **g
)

func allgadd(gp *g) {
	if readgstatus(gp) == _Gidle {
		throw("allgadd: bad status Gidle")
	}

	lock(&allglock)
	allgs = append(allgs, gp)
	if &allgs[0] != allgptr {
		atomicstorep(unsafe.Pointer(&allgptr), unsafe.Pointer(&allgs[0]))
	}
	atomic.Storeuintptr(&allglen, uintptr(len(allgs)))
	unlock(&allglock)
}

// allGsSnapshot返回所有Gs切片的快照。
// 
// 世界必须被阻止，否则allglock必须被扣押。
func allGsSnapshot() []*g {
	assertWorldStoppedOrLockHeld(&allglock)

	// 因为世界停止或allglock被控制，allgadd 
	// 不能与此同时发生。allgs单调增长，现有条目永远不会更改，因此我们可以简单地返回一个切片头的副本。为了增加安全性，
	// 我们对len进行了删减，因为它仍然可以改变。
	return allgs[:len(allgs):len(allgs)]
}

// atomicAllG返回&allgs[0]和len（allgs）用于atomicAllGIndex。
func atomicAllG() (**g, uintptr) {
	length := atomic.Loaduintptr(&allglen)
	ptr := (**g)(atomic.Loadp(unsafe.Pointer(&allgptr)))
	return ptr, length
}

// atomicAllGIndex返回ptr[i]，其中包含从atomicAllG返回的allgptr。
func atomicAllGIndex(ptr **g, i uintptr) *g {
	return *(**g)(add(unsafe.Pointer(ptr), i*goarch.PtrSize))
}

// forEachG从allgs对每个G呼叫fn。
// 
// forEachG获取一个锁以排除并发添加的新Gs。
func forEachG(fn func(gp *g)) {
	lock(&allglock)
	for _, gp := range allgs {
		fn(gp)
	}
	unlock(&allglock)
}

// forEachGRace从allgs向每个G呼叫fn。
// 
// forEachGRace避免锁定，但不排除在
// 执行期间添加新的Gs，这可能会错过。
func forEachGRace(fn func(gp *g)) {
	ptr, length := atomicAllG()
	for i := uintptr(0); i < length; i++ {
		gp := atomicAllGIndex(ptr, i)
		fn(gp)
	}
	return
}

const (
	// 要从sched获取的goroutine ID数。立即转到本地per-P缓存。
	// 16似乎提供了足够的摊销，但除此之外，它大多是任意数字。
	_GoidCacheBatch = 16
)

// cpuinit从类似Unix的操作系统上的环境中提取环境变量GODEBUG，并调用internal/cpu。初始化。
func cpuinit() {
	const prefix = "GODEBUG="
	var env string

	switch GOOS {
	case "aix", "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd", "illumos", "solaris", "linux":
		cpu.DebugOptions = true

		// 类似于goenv_unix，但直接提取
		// GODEBUG的环境值。
		// TODO（moehrmann）：在cpuinit（）之前调用general goenvs（）时删除
		n := int32(0)
		for argv_index(argv, argc+1+n) != nil {
			n++
		}

		for i := int32(0); i < n; i++ {
			p := argv_index(argv, argc+1+i)
			s := *(*string)(unsafe.Pointer(&stringStruct{unsafe.Pointer(p), findnull(p)}))

			if hasPrefix(s, prefix) {
				env = gostring(p)[len(prefix):]
				break
			}
		}
	}

	cpu.Initialize(env)

	// Support cpu功能变量用于编译器生成的代码
	// ，以保护无法假定始终支持的指令的执行。
	switch GOARCH {
	case "386", "amd64":
		x86HasPOPCNT = cpu.X86.HasPOPCNT
		x86HasSSE41 = cpu.X86.HasSSE41
		x86HasFMA = cpu.X86.HasFMA

	case "arm":
		armHasVFPv4 = cpu.ARM.HasVFPv4

	case "arm64":
		arm64HasATOMICS = cpu.ARM64.HasATOMICS
	}
}

// 引导序列是：
// 
// 调用OSIT 
// 调用调度
// 创建并排队新建G 
// 调用运行时·mstart 
// 
// 新G调用运行时·main。
func schedinit() {
	lockInit(&sched.lock, lockRankSched)
	lockInit(&sched.sysmonlock, lockRankSysmon)
	lockInit(&sched.deferlock, lockRankDefer)
	lockInit(&sched.sudoglock, lockRankSudog)
	lockInit(&deadlock, lockRankDeadlock)
	lockInit(&paniclk, lockRankPanic)
	lockInit(&allglock, lockRankAllg)
	lockInit(&allpLock, lockRankAllp)
	lockInit(&reflectOffs.lock, lockRankReflectOffs)
	lockInit(&finlock, lockRankFin)
	lockInit(&trace.bufLock, lockRankTraceBuf)
	lockInit(&trace.stringsLock, lockRankTraceStrings)
	lockInit(&trace.lock, lockRankTrace)
	lockInit(&cpuprof.lock, lockRankCpuprof)
	lockInit(&trace.stackTab.lock, lockRankTraceStackTab)
	// 强制执行此锁始终是叶锁。
	// 这把锁的所有关键部分都应该非常短。
	lockInit(&memstats.heapStats.noPLock, lockRankLeafRank)

	// raceinit必须是对种族检测器的第一个调用。
	// 尤其是，必须在下面的Mallocint调用racemapshadow之前完成。世界开始停止了。
	_g_ := getg()
	if raceenabled {
		_g_.racectx, raceprocctx0 = raceinit()
	}

	sched.maxmcount = 10000

	worldStopped()

	moduledataverify()
	stackinit()
	mallocinit()
	cpuinit()      // 必须在alginit之前运行
	alginit()      // 在调用
	fastrandinit() // 必须在MCommonini 
	mcommoninit(_g_.m, -1)
	modulesinit()   // 提供活动模块
	typelinksinit() // 使用映射之前使用maps、hash、fastrand，activeModules 
	itabsinit()     // 使用activeModules 
	stkobjinit()    // 必须在GC启动前运行

	sigsave(&_g_.m.sigmask)
	initSigmask = _g_.m.sigmask

	if offset := unsafe.Offsetof(sched.timeToRun); offset%8 != 0 {
		println(offset)
		throw("sched.timeToRun not aligned to 8 bytes")
	}

	goargs()
	goenvs()
	parsedebugvars()
	gcinit()

	lock(&sched.lock)
	sched.lastpoll = uint64(nanotime())
	procs := ncpu
	if n, ok := atoi32(gogetenv("GOMAXPROCS")); ok && n > 0 {
		procs = n
	}
	if procresize(procs) != nil {
		throw("unknown runnable goroutine during bootstrap")
	}
	unlock(&sched.lock)

	// 世界现在有效启动，因为P可以运行。
	worldStarted()

	// 对于cgocheck>1，我们始终打开写屏障
	// 并检查所有指针写入。我们必须在
	// procresize之后才能执行此操作，因为写屏障需要一个P。
	if debug.cgocheck > 1 {
		writeBarrier.cgo = true
		writeBarrier.enabled = true
		for _, p := range allp {
			p.wbBuf.reset()
		}
	}

	if buildVersion == "" {
		// 条件永远不会触发。此代码仅用于
		// 以确保运行时·buildVersion保存在生成的二进制文件中。
		buildVersion = "unknown"
	}
	if len(modinfo) == 1 {
		// 不应触发该条件。这段代码只用于
		// 以确保runtime·modinfo保存在生成的二进制文件中。
		modinfo = ""
	}
}

func dumpgstatus(gp *g) {
	_g_ := getg()
	print("runtime: gp: gp=", gp, ", goid=", gp.goid, ", gp->atomicstatus=", readgstatus(gp), "\n")
	print("runtime:  g:  g=", _g_, ", goid=", _g_.goid, ",  g->atomicstatus=", readgstatus(_g_), "\n")
}

// sched。锁必须锁好。
func checkmcount() {
	assertLockHeld(&sched.lock)

	if mcount() > sched.maxmcount {
		print("runtime: program exceeds ", sched.maxmcount, "-thread limit\n")
		throw("thread exhaustion")
	}
}

// mReserveID返回下一个用于新m的ID。这个新m立即被checkdead视为“正在运行”。
// 
// sched。锁必须锁好。
func mReserveID() int64 {
	assertLockHeld(&sched.lock)

	if sched.mnext+1 < sched.mnext {
		throw("runtime: thread ID overflow")
	}
	id := sched.mnext
	sched.mnext++
	checkmcount()
	return id
}

// 预分配的ID可以作为“ID”传递，也可以通过传递-1来忽略。
func mcommoninit(mp *m, id int64) {
	_g_ := getg()

	// g0堆栈对用户来说没有意义（也不一定是不可理解的）。
	if _g_ != _g_.m.g0 {
		callers(1, mp.createstack[:])
	}

	lock(&sched.lock)

	if id >= 0 {
		mp.id = id
	} else {
		mp.id = mReserveID()
	}

	lo := uint32(int64Hash(uint64(mp.id), fastrandseed))
	hi := uint32(int64Hash(uint64(cputicks()), ^fastrandseed))
	if lo|hi == 0 {
		hi = 1
	}
	// 与1.17的行为相同。
	// 待办事项：简化这些。
	if goarch.BigEndian {
		mp.fastrand = uint64(lo)<<32 | uint64(hi)
	} else {
		mp.fastrand = uint64(hi)<<32 | uint64(lo)
	}

	mpreinit(mp)
	if mp.gsignal != nil {
		mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard
	}

	// 添加到allm，这样垃圾收集器就不会在寄存器或线程本地存储中释放g->m 
	// 了。
	mp.alllink = allm

	// NumCgoCall（）在没有schedlock的情况下迭代allm，因此我们需要安全地发布它。
	atomicstorep(unsafe.Pointer(&allm), unsafe.Pointer(mp))
	unlock(&sched.lock)

	// 如果cgo调用崩溃，请分配内存来保存cgo回溯。
	if iscgo || GOOS == "solaris" || GOOS == "illumos" || GOOS == "windows" {
		mp.cgoCallers = new(cgoCallers)
	}
}

var fastrandseed uintptr

func fastrandinit() {
	s := (*[unsafe.Sizeof(fastrandseed)]byte)(unsafe.Pointer(&fastrandseed))[:]
	getRandomData(s)
}

// 标记gp已准备好运行。
func ready(gp *g, traceskip int, next bool) {
	if trace.enabled {
		traceGoUnpark(gp, traceskip)
	}

	status := readgstatus(gp)

	// 标记runnable。
	_g_ := getg()
	mp := acquirem() // 禁用抢占，因为它可以在本地变量
	if status&^_Gscan != _Gwaiting {
		dumpgstatus(gp)
		throw("bad g->status in ready")
	}

	// 状态为Gwaiting或Gscanwaiting，使Grunnable并打开runq 
	casgstatus(gp, _Gwaiting, _Grunnable)
	runqput(_g_.m.p.ptr(), gp, next)
	wakep()
	releasem(mp)
}

// 冻结停止等待是冻结世界设置
// sched的一个大值。stopwait to以请求所有Gs永久停止。
const freezeStopWait = 0x7fffffff

// 如果运行时试图冻结
// 世界，则冻结设置为非零。
var freezing uint32

// 类似于Stop the World，但尽了最大努力，可以多次调用。
// 没有反向操作，在崩溃时使用。
// 此函数不能锁定任何互斥锁。
func freezetheworld() {
	atomic.Store(&freezing, 1)
	// 停止等待和抢占请求可能会丢失
	// 因为与并发执行的线程竞争，
	// 因此，请多次尝试
	for i := 0; i < 5; i++ {
		// 这应该告诉计划程序不要启动任何新的goroutines 
		sched.stopwait = freezeStopWait
		atomic.Store(&sched.gcwaiting, 1)
		// 这应该停止运行goroutines 
		if !preemptall() {
			break // 不运行goroutines 
		}
		usleep(1000)
	}
	// 确保
	usleep(1000)
	preemptall()
	usleep(1000)
}

// 所有g状态的读写都通过readgstatus，casgstatus 
// castogscanstatus，casfrom_Gscanstatus。
// go:nosplit 
func readgstatus(gp *g) uint32 {
	return atomic.Load(&gp.atomicstatus)
}

// gscanstatus的行为就像锁一样，这会释放它们。
// 如果它被证明是一个性能的打击，我们应该能够使这些
// 简单的原子存储，但现在我们要抛出，如果
// 我们看到一个不一致的状态。
func casfrom_Gscanstatus(gp *g, oldval, newval uint32) {
	success := false

	// 检查转换是否有效。
	switch oldval {
	default:
		print("runtime: casfrom_Gscanstatus bad oldval gp=", gp, ", oldval=", hex(oldval), ", newval=", hex(newval), "\n")
		dumpgstatus(gp)
		throw("casfrom_Gscanstatus:top gp->status is not in scan state")
	case _Gscanrunnable,
		_Gscanwaiting,
		_Gscanrunning,
		_Gscansyscall,
		_Gscanpreempted:
		if newval == oldval&^_Gscan {
			success = atomic.Cas(&gp.atomicstatus, oldval, newval)
		}
	}
	if !success {
		print("runtime: casfrom_Gscanstatus failed gp=", gp, ", oldval=", hex(oldval), ", newval=", hex(newval), "\n")
		dumpgstatus(gp)
		throw("casfrom_Gscanstatus: gp->status is not in scan state")
	}
	releaseLockRank(lockRankGscan)
}

// 如果gp未处于预期状态且cas失败，则返回false。
// 这就像一个锁获取，而casfromgstatus就像一个锁释放。
func castogscanstatus(gp *g, oldval, newval uint32) bool {
	switch oldval {
	case _Grunnable,
		_Grunning,
		_Gwaiting,
		_Gsyscall:
		if newval == oldval|_Gscan {
			r := atomic.Cas(&gp.atomicstatus, oldval, newval)
			if r {
				acquireLockRank(lockRankGscan)
			}
			return r

		}
	}
	print("runtime: castogscanstatus oldval=", hex(oldval), " newval=", hex(newval), "\n")
	throw("castogscanstatus")
	panic("not reached")
}

// 如果被要求进入或退出GSCAN状态，将抛出。改用castogscanstatus 
// 和casfrom_Gscanstatus。
// 如果g->atomicstatus处于Gscan状态，casgstatus将循环，直到
// 将其置于Gscan状态的例程完成。
// go:nosplit 
func casgstatus(gp *g, oldval, newval uint32) {
	if (oldval&_Gscan != 0) || (newval&_Gscan != 0) || oldval == newval {
		systemstack(func() {
			print("runtime: casgstatus: oldval=", hex(oldval), " newval=", hex(newval), "\n")
			throw("casgstatus: bad incoming values")
		})
	}

	acquireLockRank(lockRankGscan)
	releaseLockRank(lockRankGscan)

	// 请参阅https:
	const yieldDelay = 5 * 1000
	var nextYield int64

	// 如果gp->atomicstatus处于扫描状态，则循环给
	// GC完成并将状态更改为oldval的时间。
	for i := 0; !atomic.Cas(&gp.atomicstatus, oldval, newval); i++ {
		if oldval == _Gwaiting && gp.atomicstatus == _Grunnable {
			throw("casgstatus: waiting for Gwaiting but is Grunnable")
		}
		if i == 0 {
			nextYield = nanotime() + yieldDelay
		}
		if nanotime() < nextYield {
			for x := 0; x < 10 && gp.atomicstatus != oldval; x++ {
				procyield(1)
			}
		} else {
			osyield()
			nextYield = nanotime() + yieldDelay/2
		}
	}

	// 处理调度延迟的跟踪。
	if oldval == _Grunning {
		// 每8次goroutine退出跑步时跟踪一次。
		if gp.trackingSeq%gTrackingPeriod == 0 {
			gp.tracking = true
		}
		gp.trackingSeq++
	}
	if gp.tracking {
		if oldval == _Grunnable {
			// 我们已经无法运行了，所以请测量我们在这个状态下花费了多少时间，并将其添加到
			// 运行时间中。
			now := nanotime()
			gp.runnableTime += now - gp.runnableStamp
			gp.runnableStamp = 0
		}
		if newval == _Grunnable {
			// 我们刚刚转换到runnable，所以记录发生的时间。
			now := nanotime()
			gp.runnableStamp = now
		} else if newval == _Grunning {
			// 我们正在过渡到跑步，所以关闭
			// 跟踪并记录我们在
			// runnable上花费的时间。
			gp.tracking = false
			sched.timeToRun.record(gp.runnableTime)
			gp.runnableTime = 0
		}
	}
}

// casgstatus（gp，oldstatus，Gcopystack），假设oldstatus是Gwaiting或Grunnable。
// 返回旧状态。无法直接调用casgstatus，因为我们正在使用可能来自netpoll的
// 异步唤醒。如果我们从readgstatus上看到Gwaiting，
// 当我们到达cas时，它可能已经变得难以接受了。如果我们调用casgstatus，
// 它将循环等待状态返回到Gwaiting，它永远不会返回。
// go:nosplit 
func casgcopystack(gp *g) uint32 {
	for {
		oldstatus := readgstatus(gp) &^ _Gscan
		if oldstatus != _Gwaiting && oldstatus != _Grunnable {
			throw("copystack: bad status, not Gwaiting or Grunnable")
		}
		if atomic.Cas(&gp.atomicstatus, oldstatus, _Gcopystack) {
			return oldstatus
		}
	}
}

// casGToPreemptScan转换gp从_Grunning到_Gscan |。
// 
// TODO（奥斯汀）：这是唯一一个既改变状态又锁定_Gscan位的状态操作。重新思考这个问题。
func casGToPreemptScan(gp *g, old, new uint32) {
	if old != _Grunning || new != _Gscan|_Gpreempted {
		throw("bad g transition")
	}
	acquireLockRank(lockRankGscan)
	for !atomic.Cas(&gp.atomicstatus, _Grunning, _Gscan|_Gpreempted) {
	}
}

// Casgfrom阻止了gp从_gprempted过渡到
// u Gwaiting的尝试。如果成功，调用方负责
// 重新调度gp。
func casGFromPreempted(gp *g, old, new uint32) bool {
	if old != _Gpreempted || new != _Gwaiting {
		throw("bad g transition")
	}
	return atomic.Cas(&gp.atomicstatus, _Gpreempted, _Gwaiting)
}

// stop the World停止所有P执行goroutines，中断
// GC安全点的所有goroutines，并将原因记录为停止的原因
// 。返回时，只有当前goroutine的P正在运行。
// 停止World不能从系统堆栈调用，调用方
// 不能持有worldsema。当
// 其他P应恢复执行时，调用方必须调用startTheWorld。
// 
// 停止世界对于多个goroutines同时调用
// 是安全的。每个都将执行自己的停止，并且这些停止将被序列化。
// 
// 执行堆栈转储的例程也会使用此选项。如果系统处于紧急状态或退出状态，这可能无法可靠地停止所有
// goroutines。
func stopTheWorld(reason string) {
	semacquire(&worldsema)
	gp := getg()
	gp.m.preemptoff = reason
	systemstack(func() {
		// 标记名为stopTheWorld可抢占的goroutine，以便扫描其
		// 堆栈。
		// 这让马克工人在我们试图阻止世界时扫描我们，因为否则我们可能会陷入相互抢占僵局。
		// 我们不能修改G堆栈上的任何内容，因为可能会发生堆栈收缩。堆栈收缩在其他方面是可以的，因为为了从该函数返回（并离开系统堆栈），我们必须抢占所有goroutine，包括任何试图扫描堆栈的操作，在这种情况下，任何堆栈收缩都将在我们退出时完成。
		casgstatus(gp, _Grunning, _Gwaiting)
		stopTheWorldWithSema()
		casgstatus(gp, _Gwaiting, _Grunning)
	})
}

// start the world撤销stop the world的效果。
func startTheWorld() {
	systemstack(func() { startTheWorldWithSema(false) })

	// worldsema必须在WorldWithSema启动后进行，以确保在worldsema启动期间，gomaxprocs无法更改。
	// 
	// 释放worldsema，直接交给下一位服务员，但
	// 获得了我，这样semrelease1就不会试图让我们浪费时间。
	// 
	// 否则，如果ReadMemStats在循环中被调用，则
	// 它可能会阻止其他阻止世界的尝试，例如用于启动或结束GC的
	// 。这个模块的操作非常重，我们应该尽可能公平。
	// 
	// 我们不想在现在
	// 和释放信号量之间被抢占，因为这样我们会让每个人
	// （包括，例如，地面军事系统）等待更长时间。
	mp := acquirem()
	mp.preemptoff = ""
	semrelease1(&worldsema, true, 0)
	releasem(mp)
}

// stopTheWorld GC与stopTheWorld具有相同的效果，但会阻止
// 直到GC不运行。它还阻止GC启动
// 直到调用startTheWorldGC。
func stopTheWorldGC(reason string) {
	semacquire(&gcsema)
	stopTheWorld(reason)
}

// startTheWorldGC撤销stopTheWorldGC的效果。
func startTheWorldGC() {
	startTheWorld()
	semrelease(&gcsema)
}

// 持有worldsema赋予了M试图阻止世界的权利。
var worldsema uint32 = 1

// 持有gcsema授予M阻止GC的权利，并阻止
// 直到当前GC完成。特别是，它防止gomaxprocs 
// 同时更改。
// 
// TODO（mknyszek）：一旦gomaxprocs和执行跟踪程序能够处理在GC期间被更改/启用的
// ，请删除此项。
var gcsema uint32 = 1

// StopTheWorldWithEMA是stopTheWorld的核心实现。
// 调用方负责获取worldsema并禁用
// 先抢占，然后在系统上停止WorldWithSema
// 
// m.preemptoff=”“
// systemstack（startTheWorldWithEMA）
// semrelease（&worldsema）
// 
// 允许获取一次worldsema，然后执行多个
// 用sema开始世界/用sema对停止世界。
// 其他P能够在连续调用
// 以EMA启动World并以EMA停止World之间执行。
// 持有worldsema会导致调用
// 停止World的任何其他Goroutine被阻止。
func stopTheWorldWithSema() {
	_g_ := getg()

	// 如果我们持有一个锁，那么我们将无法阻止另一个试图获取锁的M 
	// 被阻止。
	if _g_.m.locks > 0 {
		throw("stopTheWorld: holding locks")
	}

	lock(&sched.lock)
	sched.stopwait = gomaxprocs
	atomic.Store(&sched.gcwaiting, 1)
	preemptall()
	// 停止电流P 
	_g_.m.p.ptr().status = _Pgcstop // Pgcstop仅用于诊断。
	sched.stopwait--
	// 尝试重新获取Psyscall状态下的所有P 
	for _, p := range allp {
		s := p.status
		if s == _Psyscall && atomic.Cas(&p.status, s, _Pgcstop) {
			if trace.enabled {
				traceGoSysBlock(p)
				traceProcStop(p)
			}
			p.syscalltick++
			sched.stopwait--
		}
	}
	// 停止空闲P的
	for {
		p := pidleget()
		if p == nil {
			break
		}
		p.status = _Pgcstop
		sched.stopwait--
	}
	wait := sched.stopwait > 0
	unlock(&sched.lock)

	// 等待剩余P自动停止
	if wait {
		for {
			// 等待100us，然后在任何种族
			if notetsleep(&sched.stopnote, 100*1000) {
				noteclear(&sched.stopnote)
				break
			}
			preemptall()
		}
	}

	// 检查
	bad := ""
	if sched.stopwait != 0 {
		bad = "stopTheWorld: not stopped (stopwait != 0)"
	} else {
		for _, p := range allp {
			if p.status != _Pgcstop {
				bad = "stopTheWorld: not stopped (status != _Pgcstop)"
			}
		}
	}
	if atomic.Load(&freezing) != 0 {
		// 其他线程正在恐慌。如果停止线程上的信号处理程序
		// 健全性检查失败。无论如何，我们应该停止这个线程。
		// 发生死机，这可能会导致上述
		lock(&deadlock)
		lock(&deadlock)
	}
	if bad != "" {
		throw(bad)
	}

	worldStopped()
}

func startTheWorldWithSema(emitTraceEvent bool) int64 {
	assertWorldStopped()

	mp := acquirem() // 禁用抢占，因为它可以在本地变量
	if netpollinited() {
		list := netpoll(0) // 非阻塞
		injectglist(&list)
	}
	lock(&sched.lock)

	procs := gomaxprocs
	if newprocs != 0 {
		procs = newprocs
		newprocs = 0
	}
	p1 := procresize(procs)
	sched.gcwaiting = 0
	if sched.sysmonwait != 0 {
		sched.sysmonwait = 0
		notewakeup(&sched.sysmonnote)
	}
	unlock(&sched.lock)

	worldStarted()

	for p1 != nil {
		p := p1
		p1 = p1.link.ptr()
		if p.m != 0 {
			mp := p.m.ptr()
			p.m = 0
			if mp.nextp != 0 {
				throw("startTheWorld: inconsistent mp->nextp")
			}
			mp.nextp.set(p)
			notewakeup(&mp.park)
		} else {
			// 启动M运行p。不要在下面启动另一个M。
			newm(nil, p, -1)
		}
	}

	// 在执行清理任务之前，捕获开始世界时间。
	startTime := nanotime()
	if emitTraceEvent {
		traceGCSTWDone()
	}

	// 如果本地队列或全局队列中有过多的可运行goroutine，请唤醒一个额外的进程。如果我们不这样做，进程将自行停止。
	// 如果我们有很多多余的工作，重置自旋将根据需要取消额外的进程。
	wakep()

	releasem(mp)

	return startTime
}

// usesLibcall指示此运行时是否通过libcall执行系统调用
// 。
func usesLibcall() bool {
	switch GOOS {
	case "aix", "darwin", "illumos", "ios", "solaris", "windows":
		return true
	case "openbsd":
		return GOARCH == "386" || GOARCH == "amd64" || GOARCH == "arm" || GOARCH == "arm64"
	}
	return false
}

// mStackIsSystemAllocated指示此运行时是否在
// 系统分配的堆栈上启动。
func mStackIsSystemAllocated() bool {
	switch GOOS {
	case "aix", "darwin", "plan9", "illumos", "ios", "solaris", "windows":
		return true
	case "openbsd":
		switch GOARCH {
		case "386", "amd64", "arm", "arm64":
			return true
		}
	}
	return false
}

// mstart是新Ms的入口点。
// 它是用汇编编写的，使用ABI0，标记为TOPFRAME，并调用mstart0。
func mstart()

// mstart0是新女士的进入点。
// 这不能分割堆栈，因为我们甚至可能还没有设置堆栈
// 边界。
// 
// 可能会在STW期间运行（因为它还没有P），所以写
// 不允许设置障碍。
// 
// go:nosplit 
// go:nowritebarrierrec 
func mstart0() {
	_g_ := getg()

	osStack := _g_.stack.lo == 0
	if osStack {
		// 从系统堆栈初始化堆栈边界。
		// Cgo可能在堆栈中留下了堆栈大小。你好
		// minit可能会更新堆栈边界。
		// 
		// 注意：这些界限可能不是很准确。
		// 我们将hi设置为&size，但有些东西高于
		// 它。1024应该用来补偿这个，但是有点武断。
		size := _g_.stack.hi
		if size == 0 {
			size = 8192 * sys.StackGuardMultiplier
		}
		_g_.stack.hi = uintptr(noescape(unsafe.Pointer(&size)))
		_g_.stack.lo = _g_.stack.hi - size + 1024
	}
	// 初始化堆栈保护，以便我们可以开始调用常规
	// Go代码。
	_g_.stackguard0 = _g_.stack.lo + _StackGuard
	// 这是g0，所以我们也可以调用go:systemstack 
	// 函数，检查stackguard1。
	_g_.stackguard1 = _g_.stackguard0
	mstart1()

	// 退出此线程。
	if mStackIsSystemAllocated() {
		// Windows、Solaris、illumos、Darwin、AIX和Plan 9始终系统分配
		// 堆栈，但将其放入。mstart之前的stack，
		// 所以上面的逻辑还没有设置osStack。
		osStack = true
	}
	mexit(osStack)
}

// go:noinline是为了保证下面的getcallersp/getcallersp是安全的，
// 这样我们就可以设置g0了。计划返回上面的mstart1调用。
// go:noinline 
func mstart1() {
	_g_ := getg()

	if _g_ != _g_.m.g0 {
		throw("bad runtime·mstart")
	}

	// 设置m.g0。sched作为一个标签，在上面mstart0中的mstart1调用之后返回
	// 供goexit0和mcall使用。
	// 在调用调度后，我们再也不会返回MSSTART1，
	// 这样其他调用可以重用当前帧。goexit0执行一个gogo，需要从mstart1返回，并让mstart0退出线程。
	_g_.sched.g = guintptr(unsafe.Pointer(_g_))
	_g_.sched.pc = getcallerpc()
	_g_.sched.sp = getcallersp()

	asminit()
	minit()

	// 安装信号处理程序；minit之后，使minit可以
	// 准备线程以便能够处理信号。
	if _g_.m == &m0 {
		mstartm0()
	}

	if fn := _g_.m.mstartfn; fn != nil {
		fn()
	}

	if _g_.m != &m0 {
		acquirep(_g_.m.nextp.ptr())
		_g_.m.nextp = 0
	}
	schedule()
}

// mstartm0实现了仅在m0上运行的mstart1的一部分。
// 
// 这里允许写屏障，因为我们知道GC还不能运行，所以它们不会运行。
// 
// go:yeswritebarrierrec 
func mstartm0() {
	// 为非go创建的线程上的回调创建额外的M。
	// 对于由
	// 系统调用创建的回调，Windows上还需要额外的M。NewCallback。详情见第6751期。
	if (iscgo || GOOS == "windows") && !cgoHasExtraM {
		cgoHasExtraM = true
		newextram()
	}
	initsig(false)
}

// mPark使线程自行停止，一旦醒来就会返回。
// go:nosplit 
func mPark() {
	gp := getg()
	notesleep(&gp.m.park)
	noteclear(&gp.m.park)
}

// mexit崩溃并退出当前线程。
// 
// 不要直接调用它来退出线程，因为它必须在线程堆栈的顶部
// 运行。相反，可以使用gogo（&_g_u.m.g0.sched）来
// 将堆栈展开到退出线程的点。
// 
// 它是用m.p.输入的！=无，因此允许写入障碍。它
// 将在退出前释放P。这是主线程。把它楔住。
func mexit(osStack bool) {
	g := getg()
	m := g.m

	if m == &m0 {
		// 
		// 在Linux上，退出主线程会使进程
		// 进入不可等待的僵尸状态。在计划9中，
		// 退出主线程解锁等待，即使
		// 其他线程仍在运行。在Solaris上，我们既不能退出线程，也不能从MSStart返回。其他
		// 不好的事情可能会发生在其他平台上。
		// 
		// 我们可以尝试在楔入
		// 之前进一步清理这个M，但这会使信号处理复杂化。
		handoffp(releasep())
		lock(&sched.lock)
		sched.nmfreed++
		checkdead()
		unlock(&sched.lock)
		mPark()
		throw("locked m0 woke up")
	}

	sigblock(true)
	unminit()

	// 释放gsignal堆栈。
	if m.gsignal != nil {
		stackfree(m.gsignal.stack)
		// 在某些平台上，当调用VDSO（例如nanotime）
		// 我们将g存储在gsignal堆栈上（如果有）。
		// 现在堆栈已被释放，请将其与m解除链接，因此我们在调用VDSO代码时不会对其进行写入。
		m.gsignal = nil
	}

	// 从allm中删除m。
	lock(&sched.lock)
	for pprev := &allm; *pprev != nil; pprev = &(*pprev).alllink {
		if *pprev == m {
			*pprev = m.alllink
			goto found
		}
	}
	throw("m not found in allm")
found:
	if !osStack {
		// 延迟收割m，直到完成堆叠。
		// 
		// 如果这是使用操作系统堆栈，操作系统将释放它
		// 因此不需要收割。
		atomic.Store(&m.freeWait, 1)
		// 把m放在自由列表上，尽管它在自由等待为0之前不会收获。请注意，自由列表不能通过alllink链接到
		// 上，因为有些函数在没有
		// 锁定的情况下遍历allm，所以可能会使用alllink。
		m.freelink = sched.freem
		sched.freem = m
	}
	unlock(&sched.lock)

	atomic.Xadd64(&ncgocall, int64(m.ncgocall))

	// 释放P.
	handoffp(releasep())
	// 在这一点之后，我们不能有写障碍。

	// 调用死锁检测器。这必须发生在
	// 移交之后，因为它可能已经开始了一个新的M，以取代我们的
	// 移交工作。
	lock(&sched.lock)
	sched.nmfreed++
	checkdead()
	unlock(&sched.lock)

	if GOOS == "darwin" || GOOS == "ios" {
		// 确保M退出时pendingPreemptSignals正确。
		// For#41702。
		if atomic.Load(&m.signalPending) != 0 {
			atomic.Xadd(&pendingPreemptSignals, -1)
		}
	}

	// 销毁所有分配的资源。调用后，我们可能不再使用任何锁。
	mdestroy(m)

	if osStack {
		// 从mstart返回并让系统线程
		// 库释放g0堆栈并终止线程。
		return
	}

	// mstart是线程的入口点，因此没有什么可返回的。直接退出线程。exitThread将清除
	// m.freeWait，当它处理完堆栈时，m可以被
	// 。
	exitThread(&m.freeWait)
}

// 当p到达GC安全点时，forEachP为每个p调用fn（p）。
// 如果P当前正在执行代码，这将把P带到GC 
// 安全点并在该P上执行fn。如果P没有执行代码
// （它是空闲的或在系统调用中），这将直接调用fn（P），而
// 将阻止P退出其状态。这并不能确保
// fn将在每个执行Go代码的CPU上运行，但它充当了一个全局
// 内存屏障。GC将其用作“破败的屏障”
// 
// 呼叫方必须持有worldsema。
// 
// go:systemstack 
func forEachP(fn func(*p)) {
	mp := acquirem()
	_p_ := getg().m.p.ptr()

	lock(&sched.lock)
	if sched.safePointWait != 0 {
		throw("forEachP: sched.safePointWait != 0")
	}
	sched.safePointWait = gomaxprocs - 1
	sched.safePointFn = fn

	// 要求所有Ps运行安全点函数。
	for _, p := range allp {
		if p != _p_ {
			atomic.Store(&p.runSafePointFn, 1)
		}
	}
	preemptall()

	// 从现在开始，任何进入_Pidle或_Psyscall的P将遵守
	// P.runSafePointFn==1，并在
	// 将其状态更改为_Pidle/_Psyscall时调用runSafePointFn。

	// 为所有空闲Ps.sched运行安全点函数。皮德尔不会改变，因为我们坚持计划。锁
	for p := sched.pidle.ptr(); p != nil; p = p.link.ptr() {
		if atomic.Cas(&p.runSafePointFn, 1, 0) {
			fn(p)
			sched.safePointWait--
		}
	}

	wait := sched.safePointWait > 0
	unlock(&sched.lock)

	// 为当前P运行fn。
	fn(_p_)

	// 强制当前处于_Psyscall中的Ps进入_Pidle并将其交给
	// 关闭，以诱导安全点函数的执行。
	for _, p := range allp {
		s := p.status
		if s == _Psyscall && p.runSafePointFn == 1 && atomic.Cas(&p.status, s, _Pidle) {
			if trace.enabled {
				traceGoSysBlock(p)
				traceProcStop(p)
			}
			p.syscalltick++
			handoffp(p)
		}
	}

	// 等待剩余的Ps运行fn。
	if wait {
		for {
			// 等待100秒，然后尝试在任何比赛中重新抢占。
			// 
			// 需要系统堆栈。
			if notetsleep(&sched.safePointNote, 100*1000) {
				noteclear(&sched.safePointNote)
				break
			}
			preemptall()
		}
	}
	if sched.safePointWait != 0 {
		throw("forEachP: not done")
	}
	for _, p := range allp {
		if p.runSafePointFn != 0 {
			throw("forEachP: P did not run fn")
		}
	}

	lock(&sched.lock)
	sched.safePointFn = nil
	unlock(&sched.lock)
	releasem(mp)
}

// runSafePointFn为此P运行安全点函数（如果有）。
// 这应该像
// 
// if getg（）一样调用。m、 p.runSafePointFn！=0{
// runSafePointFn（）
// }
// 
// runSafePointFn必须在任何到_Pidle或
// Psyscall的转换中进行检查，以避免forEachP在P进入_Pidle
// 安全点功能。
func runSafePointFn() {
	p := getg().m.p.ptr()
	// 解决代表该P运行安全点
	// 函数的forEachP与直接运行安全点函数的该P之间的竞争。
	if !atomic.Cas(&p.runSafePointFn, 1, 0) {
		return
	}
	sched.safePointFn(p)
	lock(&sched.lock)
	sched.safePointWait--
	if sched.safePointWait == 0 {
		notewakeup(&sched.safePointNote)
	}
	unlock(&sched.lock)
}

// 在使用cgo运行时，我们调用_cgo_thread_start 
// 为我们启动线程，以便我们可以很好地使用
// 外来代码。
var cgoThreadStart unsafe.Pointer

type cgothreadstart struct {
	g   guintptr
	tls *uint64
	fn  unsafe.Pointer
}

// 分配一个与任何线程无关的新m。
// 如果需要，可以使用p作为分配上下文。
// fn被记录为新m的m.mstartfn。
// id是可选的预分配m id。通过传递-1忽略。
// 
// 即使调用方
// 不是因为它借用了。
// 
// go:yeswritebarrierrec 
func allocm(_p_ *p, fn func(), id int64) *m {
	allocmLock.rlock()

	// 呼叫方拥有u_u，但我们可以借用（即收购）它。我们必须禁用抢占，以确保它不会被盗，这将使呼叫方失去所有权。
	acquirem()

	_g_ := getg()
	if _g_.m.p == 0 {
		acquirep(_p_) // 在这个函数中暂时借用mallocs的p 
	}

	// 释放自由M列表。我们需要在某个地方这样做，这可能会释放出我们可以使用的堆栈。
	if sched.freem != nil {
		lock(&sched.lock)
		var newList *m
		for freem := sched.freem; freem != nil; {
			if freem.freeWait != 0 {
				next := freem.freelink
				freem.freelink = newList
				newList = freem
				freem = next
				continue
			}
			// stackfree必须在系统堆栈上，但allocm是
			// 可从
			// startm从系统堆栈下过渡访问。
			systemstack(func() {
				stackfree(freem.g0.stack)
			})
			freem = freem.freelink
		}
		sched.freem = newList
		unlock(&sched.lock)
	}

	mp := new(m)
	mp.mstartfn = fn
	mcommoninit(mp, id)

	// 对于cgo或Solaris或illumos或Darwin，pthread_create将使我们成为一个堆栈。
	// Windows和Plan 9将在操作系统堆栈上布局sched堆栈。
	if iscgo || mStackIsSystemAllocated() {
		mp.g0 = malg(-1)
	} else {
		mp.g0 = malg(8192 * sys.StackGuardMultiplier)
	}
	mp.g0.m = mp

	if _p_ == _g_.m.p.ptr() {
		releasep()
	}

	releasem(_g_.m)
	allocmLock.runlock()
	return mp
}

// 当cgo回调发生在没有m（非Go创建的线程）的
// 线程上时，调用needm。
// 在这种情况下，needm需要找到一个m来使用
// 并返回正确初始化的m，g。
// 由于m和g现在没有设置（可能为零，但请参见下文）
// needm在它可以调用的例程方面受到限制。尤其是
// 它只能调用nosplit函数（textflag 7），不能执行任何需要m的调度。
// 
// 为了避免在这里需要重载，我们采用
// 以下策略：有一堆可用的m的
// 那是可以偷的。使用compare and swap 
// 从堆栈中弹出具有ABA竞争，因此我们通过交换（通过Casuintptr）模拟
// 一个锁来窃取堆栈
// 头并用MLOCKED（1）替换顶部指针。
// 这是一个简单的旋转锁，我们甚至可以使用不带m的
// 线程来锁定堆栈
// 通过存储有效的堆栈头指针来解锁堆栈。
// 
// 为了确保总是有一个m结构
// 可以被盗，我们保持不变，即
// 总是比需要的多一个。在
// 程序的开头（如果cgo正在使用），列表中植入了一个m。
// 如果needm发现它已经从列表中删除了最后一个m，那么它的工作
// 是——一旦它安装了自己的m，这样它就可以执行
// 分配内存之类的操作——创建一个备用m并将其放入列表。
// 
// 这些额外的m中的每一个都有一个g0和一个curg，它们是
// 在cgo回调期间作为调度堆栈和当前
// goroutine被压入服务。
// 
// 当m完成回调时，它会调用dropm到
// 将m放回列表中。
// go:nosplit 
func needm() {
	if (iscgo || GOOS == "windows") && !cgoHasExtraM {
		// 如果C/C++代码调用从全局构造函数进行，则可能发生这种情况。
		// 如果全局ctor使用syscall创建的
		// 回调，也可能在Windows上发生。NewCallback。详情请参见第6751期
		// 了解。
		// 
		// 无法抛出，因为计划程序尚未初始化。
		write(2, unsafe.Pointer(&earlycgocallback[0]), int32(len(earlycgocallback)))
		exit(1)
	}

	// 在获得M之前保存并阻止信号。
	// 信号处理程序可能会调用needm本身，
	// 我们必须避免死锁。此外，一旦安装了g，任何传入信号都将尝试执行，但在minit结束之前，我们不会适当设置sigaltstack设置和其他数据，这将解除信号锁定。这和
	// 通过newosproc启动一个新的m来运行Go代码时一样。
	var sigmask sigset
	sigsave(&sigmask)
	sigblock(false)

	// 锁定额外列表，获取头部，解锁弹出列表。
	// nilook=false在这里是安全的，因为上面的不变量
	// 额外列表总是包含或很快将包含
	// 至少一个m.
	mp := lockextra(false)

	// 在我们刚刚清空列表时设置needextram，
	// 因此，对cgocallbackg的最终调用将
	// 为额外列表分配一个新的m。我们将
	// 分配推迟到那时，以便在exitsyscall确保
	// 运行正常后（即，现在没有垃圾收集
	// 运行）可以完成分配。
	mp.needextram = mp.schedlink == 0
	extraMCount--
	unlockextra(mp.schedlink.ptr())

	// 存储原始信号掩码以供minit使用。
	mp.sigmask = sigmask

	// 在某些平台上安装TLS（之前的setg 
	// 将在必要时执行此操作）。
	osSetupTLS(mp)

	// 安装g（=m->g0）并设置堆栈边界
	// 以匹配当前堆栈。我们实际上不知道
	// 堆栈有多大，就像我们不知道任何
	// 调度堆栈有多大一样，但我们假设至少有32KB，
	// 这对我们来说已经足够了。
	setg(mp.g0)
	_g_ := getg()
	_g_.stack.hi = getcallersp() + 1024
	_g_.stack.lo = getcallersp() - 32*1024
	_g_.stackguard0 = _g_.stack.lo + _StackGuard

	// 初始化此线程以使用m.
	asminit()
	minit()

	// mp。curg现在是一个真正的goroutine。
	casgstatus(mp.curg, _Gdead, _Gsyscall)
	atomic.Xadd(&sched.ngsys, -1)
}

var earlycgocallback = []byte("fatal error: cgo callback before cgo call\n")

// newextram分配m并将其放在额外列表中。
// 它是用一个工作的本地m调用的，这样它就可以做一些事情，比如调用schedlock和allocate。
func newextram() {
	c := atomic.Xchg(&extraMWaiters, 0)
	if c > 0 {
		for i := uint32(0); i < c; i++ {
			oneNewExtraM()
		}
	} else {
		// 确保至少有一个额外的M。
		mp := lockextra(true)
		unlockextra(mp)
		if mp == nil {
			oneNewExtraM()
		}
	}
}

// OneWextram分配一个M并将其放在额外列表中。
func oneNewExtraM() {
	// 创建锁定到extra m的extra goroutine。
	// goroutine是cgo回调将在其中运行的上下文。
	// 时间表。pc将永远不会被返回，但将其设置为
	// goexit会向回溯例程说明goroutine堆栈结束的位置。
	mp := allocm(nil, nil, -1)
	gp := malg(4096)
	gp.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum
	gp.sched.sp = gp.stack.hi
	gp.sched.sp -= 4 * goarch.PtrSize // 读取稍微超出帧
	gp.sched.lr = 0
	gp.sched.g = guintptr(unsafe.Pointer(gp))
	gp.syscallpc = gp.sched.pc
	gp.syscallsp = gp.sched.sp
	gp.stktopsp = gp.sched.sp
	// malg返回状态为_Gidle。更改为
	// /之前的_Gdead，添加到GC可以看到的allg中。我们使用_Gdead来隐藏
	// 这是从追踪和堆栈扫描，因为它不是一个
	// “真正的”goroutine，直到needm抓住它。
	casgstatus(gp, _Gidle, _Gdead)
	gp.m = mp
	mp.curg = gp
	mp.lockedInt++
	mp.lockedg.set(gp)
	gp.lockedm.set(mp)
	gp.goid = int64(atomic.Xadd64(&sched.goidgen, 1))
	if raceenabled {
		gp.racectx = racegostart(abi.FuncPCABIInternal(newextram) + sys.PCQuantum)
	}
	// 为垃圾收集器设置allg 
	allgadd(gp)

	// gp现在在allg列表中，但我们不希望它是
	// 按gcount计数。增加
	// sched更“合适”。ngfree，但这需要锁定。递增ngsys 
	// 也有相同的效果。
	atomic.Xadd(&sched.ngsys, +1)

	// 将m添加到额外列表中。
	mnext := lockextra(true)
	mp.schedlink.set(mnext)
	extraMCount++
	unlockextra(mp)
}

// 当cgo回调调用了needm，但现在
// 完成回调并返回到非Go线程时，会调用dropm。
// 它将当前的m放回额外列表中。
// 
// 这里的主要开销是调用signalstack来释放
// m的信号堆栈，然后在下一次回调
// 时从这个线程调用needm。尝试将m保存到下一次是很有诱惑力的，
// 这将消除这两个成本，但下一次可能不会有
// 当前线程（Go不控制）可能退出。
// 如果我们为该线程保存了m，那么每次
// 这样的线程退出时都会出现m泄漏。相反，我们在每次
// 通话中获得并释放一个m。这些通常不应该是调度操作，只有几个
// 原子，所以成本应该很小。
// 
// TODO（rsc）：另一种方法是使用pthread_key_create为每个线程分配一个伪pthread。与我们在OS X上已经使用的pthread键不同，这个伪键永远不会被Go代码读取。它将存在
// 只有这样我们才能在线程退出时注册析构函数。
// 该析构函数会将m放回额外列表中。
// 这纯粹是性能优化。当前版本
// 中，每次cgo调用都会发生DROM，该版本仍然正确。
// 我们可能必须在有cgo的系统上保持当前版本
// 但没有pthread，如Windows。
func dropm() {
	// 清除m和g，并将m返回额外列表。
	// 调用setg后，我们只能调用nosplit函数
	// 而无需指针操作。
	mp := getg().m

	// 返回议员。凝乳至死亡状态。
	casgstatus(mp.curg, _Gsyscall, _Gdead)
	mp.curg.preemptStop = false
	atomic.Xadd(&sched.ngsys, +1)

	// 在解除监控前封锁信号。
	// Unminit注销信号处理堆栈（但在某些系统上需要g）。
	// Setg（nil）清除g，这是信号处理程序不运行Go处理程序的提示。
	// 重要的是不要试图处理这两个步骤之间的信号。
	sigmask := mp.sigmask
	sigblock(false)
	unminit()

	mnext := lockextra(true)
	extraMCount++
	mp.schedlink.set(mnext)

	setg(nil)

	// 承诺释放mp。
	unlockextra(mp)

	msigrestore(sigmask)
}

// EnsureDropM的辅助函数。
func getm() uintptr {
	return uintptr(unsafe.Pointer(getg().m))
}

var extram uintptr
var extraMCount uint32 // 受lockextra保护
var extraMWaiters uint32

// lockextra锁定额外列表并返回列表头。
// 调用者必须通过将新的列表头
// 存储到extram来解锁列表。如果Nilook是真的，那么lockextra将
// 返回一个nil列表头，如果它找到了。如果nilook为false，
// lockextra将继续等待，直到列表头不再为零。
// go:nosplit 
func lockextra(nilokay bool) *m {
	const locked = 1

	incr := false
	for {
		old := atomic.Loaduintptr(&extram)
		if old == locked {
			osyield_no_g()
			continue
		}
		if old == 0 && !nilokay {
			if !incr {
				// 将线程数增加1 
				// 等待M。
				// 这已被newextram清除。
				atomic.Xadd(&extraMWaiters, 1)
				incr = true
			}
			usleep_no_g(1)
			continue
		}
		if atomic.Casuintptr(&extram, old, locked) {
			return (*m)(unsafe.Pointer(old))
		}
		osyield_no_g()
		continue
	}
}

// go:nosplit 
func unlockextra(mp *m) {
	atomic.Storeuintptr(&extram, uintptr(unsafe.Pointer(mp)))
}

var (
	// allocmLock在allocm及其
	// 添加到allm中时被锁定读取。因此，获取写入锁将阻止
	// 创建新的Ms。
	allocmLock rwmutex

	// execLock序列化exec和克隆，以避免错误或未指定的
	// 在创建/销毁线程时执行行为。参见
	// 第19546期。
	execLock rwmutex
)

// newmHandoff包含需要新操作系统线程的m结构列表。
// newm在newm自身无法安全启动操作系统线程的情况下使用此选项。
var newmHandoff struct {
	lock mutex

	// newm指向需要新操作系统的M结构列表
	// 线程。该列表通过m.schedlink链接。
	newm muintptr

	// 等待表示当m 
	// 被放入列表时，需要通知唤醒。
	waiting bool
	wake    note

	// haveTemplateThread表示模板线程已启动
	// 。这不受锁的保护。使用cas将
	// 设置为1。
	haveTemplateThread uint32
}

// 创建一个新的m。它将从调用fn或调度程序开始。
// fn需要是静态的，而不是堆分配的闭包。
// 可能在m.p==nil的情况下运行，因此不允许使用写屏障。
// 
// id是可选的预分配m id。通过传递-1忽略。
// go:nowritebarrierrec 
func newm(fn func(), _p_ *p, id int64) {
	// allocm向allm添加了一个新的M，但它们在由
	// newm1中的操作系统或模板线程创建之前不会启动。
	// 
	// doAllThreadsSyscall要求allm中的每一个M最终都将
	// 启动并能够发出信号，即使有STW。
	// 
	// 在我们启动线程之前禁用抢占，以确保
	// 在allocm和启动新线程之间不会抢占newm，
	// 确保添加到allm的任何内容都保证最终
	// 启动。
	acquirem()

	mp := allocm(_p_, fn, id)
	mp.nextp.set(_p_)
	mp.sigmask = initSigmask
	if gp := getg(); gp != nil && gp.m != nil && (gp.m.lockedExt != 0 || gp.m.incgo) && GOOS != "plan9" {
		// 我们在一个被锁定的M或一个可能由C启动的线程
		// 上。这个线程的内核状态可能会很奇怪（用户可能出于这个目的锁定了它）。我们不想把它克隆到另一个
		// 线程中。相反，请一个已知的好线程为我们创建
		// 线程。
		// 
		// 这在计划9中被禁用。见戈朗。org/issue/22227。
		// 
		// TODO:这在Windows上可能是不必要的，因为
		// 没有在fork上建模线程创建。
		lock(&newmHandoff.lock)
		if newmHandoff.haveTemplateThread == 0 {
			throw("on a locked thread with no template thread")
		}
		mp.schedlink = newmHandoff.newm
		newmHandoff.newm.set(mp)
		if newmHandoff.waiting {
			newmHandoff.waiting = false
			notewakeup(&newmHandoff.wake)
		}
		unlock(&newmHandoff.lock)
		// M尚未启动，但模板线程不
		// 参与STW，因此它将始终处理排队的Ms和
		// 释放M是安全的。
		releasem(getg().m)
		return
	}
	newm1(mp)
	releasem(getg().m)
}

func newm1(mp *m) {
	if iscgo {
		var ts cgothreadstart
		if _cgo_thread_start == nil {
			throw("_cgo_thread_start missing")
		}
		ts.g.set(mp.g0)
		ts.tls = (*uint64)(unsafe.Pointer(&mp.tls[0]))
		ts.fn = unsafe.Pointer(abi.FuncPCABI0(mstart))
		if msanenabled {
			msanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
		}
		if asanenabled {
			asanwrite(unsafe.Pointer(&ts), unsafe.Sizeof(ts))
		}
		execLock.rlock() // 防止进程克隆。
		asmcgocall(_cgo_thread_start, unsafe.Pointer(&ts))
		execLock.runlock()
		return
	}
	execLock.rlock() // 防止进程克隆。
	newosproc(mp)
	execLock.runlock()
}

// startTemplateThread启动模板线程（如果模板线程尚未运行）。
// 
// 调用线程本身必须处于已知的良好状态。
func startTemplateThread() {
	if GOARCH == "wasm" { // wasm上还没有线程
		return
	}

	// 禁用抢占，以确保模板线程将在设置park once haveTemplateThread之前创建。
	mp := acquirem()
	if !atomic.Cas(&newmHandoff.haveTemplateThread, 0, 1) {
		releasem(mp)
		return
	}
	newm(templateThread, nil, -1)
	releasem(mp)
}

// templateThread是一个处于已知良好状态的线程，它只存在于
// 中，用于在调用线程
// 可能不处于良好状态时以已知良好状态启动新线程。
// 
// 很多程序都不需要这个，所以当我们第一次进入一个状态，可能会导致在未知状态下运行线程
// 时，templateThread是惰性启动的。
// 
// templateThread在没有P的M上运行，因此它不能有写
// 障碍。
// 
// go:nowritebarrierrec 
func templateThread() {
	lock(&sched.lock)
	sched.nmsys++
	checkdead()
	unlock(&sched.lock)

	for {
		lock(&newmHandoff.lock)
		for newmHandoff.newm != 0 {
			newm := newmHandoff.newm.ptr()
			newmHandoff.newm = 0
			unlock(&newmHandoff.lock)
			for newm != nil {
				next := newm.schedlink.ptr()
				newm.schedlink = 0
				newm1(newm)
				newm = next
			}
			lock(&newmHandoff.lock)
		}
		newmHandoff.waiting = true
		noteclear(&newmHandoff.wake)
		unlock(&newmHandoff.lock)
		notesleep(&newmHandoff.wake)
	}
}

// 停止执行当前m，直到有新工作可用。
// 返回已获取的P。
func stopm() {
	_g_ := getg()

	if _g_.m.locks != 0 {
		throw("stopm holding locks")
	}
	if _g_.m.p != 0 {
		throw("stopm holding p")
	}
	if _g_.m.spinning {
		throw("stopm spinning")
	}

	lock(&sched.lock)
	mput(_g_.m)
	unlock(&sched.lock)
	mPark()
	acquirep(_g_.m.nextp.ptr())
	_g_.m.nextp = 0
}

func mspinning() {
	// startm的调用者递增。让新M开始旋转。
	getg().m.spinning = true
}

// 安排一些M来运行p（如果需要，创建一个M）。
// 如果p==nil，则尝试获取一个空闲的p，如果没有空闲的p则什么也不做。
// 可能在m.p==nil的情况下运行，因此不允许使用写屏障。
// 如果设置了旋转，则调用者增加了nmspinning，startm将
// 在新启动的m中减少nmspinning或设置m.spinning。
// 
// 通过非零P的调用者必须从非抢占上下文调用。请参见下面的
// 对acquirem的评论。
// 
// 不能有写障碍，因为这可能在没有P的情况下调用。
// go:nowritebarrierrec 
func startm(_p_ *p, spinning bool) {
	// 禁用抢占。
	// 
	// 每个拥有的P必须有一个所有者，该所有者最终将在GC停止请求的事件中停止它。startm暂时拥有P 
	// （来自下面的参数或pidleget），并将所有权转让给
	// a 
	// a started M，后者将负责执行停止。
	// 
	// 在这个临时所有权期间必须禁用抢占，
	// 否则这个正在运行的P可能会进入GC停止，同时仍然
	// 持有这个临时P，使该P处于不确定状态，并死锁
	// STW。
	// 
	// 传递非nil P的调用方必须已经处于不可抢占的
	// 上下文中，否则在
	// startm的函数项中可能会发生这种抢占。通过nil P的呼叫者可能是可抢占的，因此我们必须在从下面的pidleget获取P之前禁用抢占。
	mp := acquirem()
	lock(&sched.lock)
	if _p_ == nil {
		_p_ = pidleget()
		if _p_ == nil {
			unlock(&sched.lock)
			if spinning {
				// 调用者增加了nmspinning，但没有空闲的Ps，因此可以撤销增量并放弃。
				if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
					throw("startm: negative nmspinning")
				}
			}
			releasem(mp)
			return
		}
	}
	nmp := mget()
	if nmp == nil {
		// 没有M可用，我们必须放弃计划。锁定并呼叫纽姆。
		// 然而，我们已经拥有了一个P来分配给M.
		// 
		// 一次计划。锁被释放，另一个G（例如，在一个系统调用中），
		// 找不到空闲的P，而checkdead找到一个可运行的G，但
		// 没有运行M，因为这个新的M还没有启动，因此
		// 陷入了明显的死锁。
		// 
		// 通过预先分配新M的ID来避免这种情况，
		// 这样在我们删除sched之前将其标记为“正在运行”。锁这个
		// 新M最终将运行调度程序来执行任何
		// 排队的G。
		id := mReserveID()
		unlock(&sched.lock)

		var fn func()
		if spinning {
			// 调用者增加了nmspining，所以在新M中设置M.spining。
			fn = mspinning
		}
		newm(fn, _p_, id)
		// 由start in newm提交的_p_u的所有权转移。
		// 抢占现在是安全的。
		releasem(mp)
		return
	}
	unlock(&sched.lock)
	if nmp.spinning {
		throw("startm: m is spinning")
	}
	if nmp.nextp != 0 {
		throw("startm: m has p")
	}
	if spinning && !runqempty(_p_) {
		throw("startm: p has runnable gs")
	}
	// 调用者增加了nmspining，因此在新的m.
	nmp.spinning = spinning
	nmp.nextp.set(_p_)
	notewakeup(&nmp.park)
	// wakeup提交的_p_u的所有权转移中设置m.spining。抢占现在
	// 安全。
	releasem(mp)
}

// 将P从系统调用中移开或锁定M。
// 始终在没有P的情况下运行，因此不允许使用写屏障。
// go:NowriteBarrierec 
func handoffp(_p_ *p) {
	// 在任何情况下，如果
	// findrunnable将返回一个G来运行，则切换必须启动一个M。

	// 如果它有本地工作，请立即启动
	if !runqempty(_p_) || sched.runqsize != 0 {
		startm(_p_, false)
		return
	}
	// 如果它有GC工作，请立即启动
	if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
		startm(_p_, false)
		return
	}
	// 没有本地工作，请检查是否没有旋转/空闲的M，
	// 否则不需要我们的帮助
	if atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) == 0 && atomic.Cas(&sched.nmspinning, 0, 1) { // TODO:快速原子
		startm(_p_, true)
		return
	}
	lock(&sched.lock)
	if sched.gcwaiting != 0 {
		_p_.status = _Pgcstop
		sched.stopwait--
		if sched.stopwait == 0 {
			notewakeup(&sched.stopnote)
		}
		unlock(&sched.lock)
		return
	}
	if _p_.runSafePointFn != 0 && atomic.Cas(&_p_.runSafePointFn, 1, 0) {
		sched.safePointFn(_p_)
		sched.safePointWait--
		if sched.safePointWait == 0 {
			notewakeup(&sched.safePointNote)
		}
	}
	if sched.runqsize != 0 {
		unlock(&sched.lock)
		startm(_p_, false)
		return
	}
	// 如果这是最后一个运行的P，并且没有人在轮询网络，
	// 需要唤醒另一个M来轮询网络。
	if sched.npidle == uint32(gomaxprocs-1) && atomic.Load64(&sched.lastpoll) != 0 {
		unlock(&sched.lock)
		startm(_p_, false)
		return
	}

	// 在调用低于
	// 的wakeNetPoller时，无法保持调度程序锁，因为wakeNetPoller可能会调用wakep，后者可能会调用startm。
	when := nobarrierWakeTime(_p_)
	pidleput(_p_)
	unlock(&sched.lock)

	if when != 0 {
		wakeNetPoller(when)
	}
}

// 尝试再添加一个P来执行G。
// 在G可运行时调用（newproc，ready）。
func wakep() {
	if atomic.Load(&sched.npidle) == 0 {
		return
	}
	// 对旋转线程保持保守
	if atomic.Load(&sched.nmspinning) != 0 || !atomic.Cas(&sched.nmspinning, 0, 1) {
		return
	}
	startm(nil, true)
}

// 停止执行锁定到g的当前m，直到g再次可运行。
// 返回已获取的P。
func stoplockedm() {
	_g_ := getg()

	if _g_.m.lockedg == 0 || _g_.m.lockedg.ptr().lockedm.ptr() != _g_.m {
		throw("stoplockedm: inconsistent locking")
	}
	if _g_.m.p != 0 {
		// 计划另一个M来运行此P。
		_p_ := releasep()
		handoffp(_p_)
	}
	incidlelocked(1)
	// 等待，直到另一个线程再次计划锁定。
	mPark()
	status := readgstatus(_g_.m.lockedg.ptr())
	if status&^_Gscan != _Grunnable {
		print("runtime:stoplockedm: lockedg (atomicstatus=", status, ") is not Grunnable or Gscanrunnable\n")
		dumpgstatus(_g_.m.lockedg.ptr())
		throw("stoplockedm: not runnable")
	}
	acquirep(_g_.m.nextp.ptr())
	_g_.m.nextp = 0
}

// 计划锁定的m运行锁定的gp。
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func startlockedm(gp *g) {
	_g_ := getg()

	mp := gp.lockedm.ptr()
	if mp == _g_.m {
		throw("startlockedm: locked to me")
	}
	if mp.nextp != 0 {
		throw("startlockedm: m has p")
	}
	// 直接将当前P切换到锁定的m 
	incidlelocked(-1)
	_p_ := releasep()
	mp.nextp.set(_p_)
	notewakeup(&mp.park)
	stopm()
}

// 停止当前m以停止世界。
// 当世界重新启动时返回。
func gcstopm() {
	_g_ := getg()

	if sched.gcwaiting == 0 {
		throw("gcstopm: not waiting for gc")
	}
	if _g_.m.spinning {
		_g_.m.spinning = false
		// 好的，把nmspinning放到这里，
		// 开始这个世界将根据需要解线程。
		if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
			throw("gcstopm: negative nmspinning")
		}
	}
	_p_ := releasep()
	lock(&sched.lock)
	_p_.status = _Pgcstop
	sched.stopwait--
	if sched.stopwait == 0 {
		notewakeup(&sched.stopnote)
	}
	unlock(&sched.lock)
	stopm()
}

// 计划gp在当前M上运行。
// 如果inheritTime为true，gp将继承
// 当前时间片中的剩余时间。否则，它将开始一个新的时间片。
// 永远不会回来。
// 
// 允许写入障碍，因为这是在
// 在多个位置获取P后立即调用的。
// 
// go:yeswritebarrierrec 
func execute(gp *g, inheritTime bool) {
	_g_ := getg()

	// 分配gp。m在进入_Grunning之前，让正在运行的Gs有一个
	// m.
	_g_.m.curg = gp
	gp.m = _g_.m
	casgstatus(gp, _Grunnable, _Grunning)
	gp.waitsince = 0
	gp.preempt = false
	gp.stackguard0 = gp.stack.lo + _StackGuard
	if !inheritTime {
		_g_.m.p.ptr().schedtick++
	}

	// 检查探查器是否需要打开或关闭。
	hz := sched.profilehz
	if _g_.m.profilehz != hz {
		setThreadCPUProfiler(hz)
	}

	if trace.enabled {
		// GoSysExit必须在我们有P时发生，但必须在GoStart之前发生。
		// 所以我们在这里发射它。
		if gp.syscallsp != 0 && gp.sysblocktraced {
			traceGoSysExit(gp.sysexitticks)
		}
		traceGoStart()
	}

	gogo(&gp.sched)
}

// 找到要执行的可运行goroutine。
// 试图从其他P窃取，从本地或全局队列获取g，轮询网络。
func findrunnable() (gp *g, inheritTime bool) {
	_g_ := getg()

	// 此处和切换中的条件必须一致：如果
	// findrunnable将返回G运行，则切换必须启动
	// an M.

top:
	_p_ := _g_.m.p.ptr()
	if sched.gcwaiting != 0 {
		gcstopm()
		goto top
	}
	if _p_.runSafePointFn != 0 {
		runSafePointFn()
	}

	now, pollUntil, _ := checkTimers(_p_, 0)

	if fingwait && fingwake {
		if gp := wakefing(); gp != nil {
			ready(gp, 0, true)
		}
	}
	if *cgo_yield != nil {
		asmcgocall(*cgo_yield, nil)
	}

	// 本地runq 
	if gp, inheritTime := runqget(_p_); gp != nil {
		return gp, inheritTime
	}

	// 全球runq 
	if sched.runqsize != 0 {
		lock(&sched.lock)
		gp := globrunqget(_p_, 0)
		unlock(&sched.lock)
		if gp != nil {
			return gp, false
		}
	}

	// 投票网络。
	// 在我们诉诸偷窃之前，这个netpoll只是一个优化。
	// 如果netpoll中没有服务生或线程被阻止，我们可以安全地跳过它。如果与被阻止线程
	// 存在任何类型的逻辑争用（例如，它已经从netpoll返回，但
	// 尚未设置lastpoll），该线程将在
	// 下执行阻止netpoll操作。
	if netpollinited() && atomic.Load(&netpollWaiters) > 0 && atomic.Load64(&sched.lastpoll) != 0 {
		if list := netpoll(0); !list.empty() { // 非阻塞
			gp := list.pop()
			injectglist(&list)
			casgstatus(gp, _Gwaiting, _Grunnable)
			if trace.enabled {
				traceGoUnpark(gp, 0)
			}
			return gp, false
		}
	}

	// 旋转Ms：从其他Ps窃取工作。
	// 
	// 将旋转Ms的数量限制为繁忙Ps数量的一半。
	// 这对于防止在使用
	// GOMAXPROCS>>1但程序并行度低。
	procs := uint32(gomaxprocs)
	if _g_.m.spinning || 2*atomic.Load(&sched.nmspinning) < procs-atomic.Load(&sched.npidle) {
		if !_g_.m.spinning {
			_g_.m.spinning = true
			atomic.Xadd(&sched.nmspinning, 1)
		}

		gp, inheritTime, tnow, w, newWork := stealWork(now)
		now = tnow
		if gp != nil {
			// 成功窃取。
			return gp, inheritTime
		}
		if newWork {
			// 可能有新定时器或GC工作；重新启动
			// 发现。
			goto top
		}
		if w != 0 && (pollUntil == 0 || w < pollUntil) {
			// 要等待的较早计时器。
			pollUntil = w
		}
	}

	// 我们无事可做。
	// 
	// 如果我们处于GC标记阶段，可以安全地扫描和涂黑对象，
	// 并且有工作要做，运行空闲时间标记而不是放弃
	// P.
	if gcBlackenEnabled != 0 && gcMarkWorkAvailable(_p_) {
		node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
		if node != nil {
			_p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
			gp := node.gp.ptr()
			casgstatus(gp, _Gwaiting, _Grunnable)
			if trace.enabled {
				traceGoUnpark(gp, 0)
			}
			return gp, false
		}
	}

	// wasm only:
	// 如果返回了回调且没有其他goroutine被唤醒，
	// 然后唤醒事件处理程序goroutine，它暂停执行
	// 直到触发回调。
	gp, otherReady := beforeIdle(now, pollUntil)
	if gp != nil {
		casgstatus(gp, _Gwaiting, _Grunnable)
		if trace.enabled {
			traceGoUnpark(gp, 0)
		}
		return gp, false
	}
	if otherReady {
		goto top
	}

	// 在我们放下P之前，拍一张allp切片的快照，
	// 一旦我们不再封锁
	// 安全点，它可以在脚下改变。我们不需要快照内容，因为
	// 所有内容都是不可变的。
	allpSnapshot := allp
	// 还有快照掩码。价值变化是可以的，但我们不能允许
	// len从我们下面变出来。
	idlepMaskSnapshot := idlepMask
	timerpMaskSnapshot := timerpMask

	// 返回P和块
	lock(&sched.lock)
	if sched.gcwaiting != 0 || _p_.runSafePointFn != 0 {
		unlock(&sched.lock)
		goto top
	}
	if sched.runqsize != 0 {
		gp := globrunqget(_p_, 0)
		unlock(&sched.lock)
		return gp, false
	}
	if releasep() != _p_ {
		throw("findrunnable: wrong p")
	}
	pidleput(_p_)
	unlock(&sched.lock)

	// 微妙的舞蹈：从旋转到非旋转的线程转换
	// 状态，可能与提交新作品同时进行。我们必须先删除nmspining，然后再次检查所有源代码（中间有存储加载内存屏障）。如果我们以另一种方式进行
	// ，那么在我们检查完所有
	// 源代码后，另一个线程可以提交工作，但在我们放弃之前；因此，没有人会
	// 解压一个线程来运行该工作。
	// 
	// 这适用于以下工作来源：
	// 
	// /*添加到per-P运行队列的Goroutines。
	// /*在per-P计时器堆上新的/修改的早期计时器。
	// /*空闲优先GC工作（barring golang.org/issue/19112）。
	// 
	// 如果我们在下面发现了新的工作，我们需要将m.spining恢复为信号
	// 以便重置spinning以解列新的工作线程（因为可能会有更多的
	// 而不是一个饥饿的goroutine）。但是，如果在发现新工作
	// 之后，我们也没有观察到空闲的Ps，那么可以跳过对新工作
	// 线程的解组：系统已完全加载，因此不需要旋转线程。
	// 另请参见文件顶部的“工作线程停止/取消连接”注释。
	wasSpinning := _g_.m.spinning
	if _g_.m.spinning {
		_g_.m.spinning = false
		if int32(atomic.Xadd(&sched.nmspinning, -1)) < 0 {
			throw("findrunnable: negative nmspinning")
		}

		// 注意正确性，只有从
		// 旋转到非旋转的最后一个M必须对
		// 进行这些重新检查，确保没有遗漏工作。我们在每一个M上执行
		// 转换，作为一个保守的更改，以监控对
		// 延迟的影响。见戈朗。org/issue/43997。

		// 再次检查所有运行队列。
		_p_ = checkRunqsNoP(allpSnapshot, idlepMaskSnapshot)
		if _p_ != nil {
			acquirep(_p_)
			_g_.m.spinning = true
			atomic.Xadd(&sched.nmspinning, 1)
			goto top
		}

		// 再次检查空闲优先级GC工作。
		_p_, gp = checkIdleGCNoP()
		if _p_ != nil {
			acquirep(_p_)
			_g_.m.spinning = true
			atomic.Xadd(&sched.nmspinning, 1)

			// 运行空闲的工人。
			_p_.gcMarkWorkerMode = gcMarkWorkerIdleMode
			casgstatus(gp, _Gwaiting, _Grunnable)
			if trace.enabled {
				traceGoUnpark(gp, 0)
			}
			return gp, false
		}

		// 最后，检查计时器是否创建或到期，同时检查
		// 从旋转转换为非旋转。
		// 
		// 请注意，我们不能在这里使用校验计时器，因为它调用
		// 调整计时器，可能需要分配内存，而这在我们没有活动的P时是不允许的。
		pollUntil = checkTimersNoP(allpSnapshot, timerpMaskSnapshot, pollUntil)
	}

	// 轮询网络直到下一个计时器。
	if netpollinited() && (atomic.Load(&netpollWaiters) > 0 || pollUntil != 0) && atomic.Xchg64(&sched.lastpoll, 0) != 0 {
		atomic.Store64(&sched.pollUntil, uint64(pollUntil))
		if _g_.m.p != 0 {
			throw("findrunnable: netpoll with p")
		}
		if _g_.m.spinning {
			throw("findrunnable: netpoll with spinning")
		}
		delay := int64(-1)
		if pollUntil != 0 {
			if now == 0 {
				now = nanotime()
			}
			delay = pollUntil - now
			if delay < 0 {
				delay = 0
			}
		}
		if faketime != 0 {
			// 当使用假时间时，只需投票即可。
			delay = 0
		}
		list := netpoll(delay) // 阻止，直到有新的工作可用
		atomic.Store64(&sched.pollUntil, 0)
		atomic.Store64(&sched.lastpoll, uint64(nanotime()))
		if faketime != 0 && list.empty() {
			// 使用假时间，但什么都没有准备好；停止M.
			// 当所有M停止时，checkdead将调用timejump。
			stopm()
			goto top
		}
		lock(&sched.lock)
		_p_ = pidleget()
		unlock(&sched.lock)
		if _p_ == nil {
			injectglist(&list)
		} else {
			acquirep(_p_)
			if !list.empty() {
				gp := list.pop()
				injectglist(&list)
				casgstatus(gp, _Gwaiting, _Grunnable)
				if trace.enabled {
					traceGoUnpark(gp, 0)
				}
				return gp, false
			}
			if wasSpinning {
				_g_.m.spinning = true
				atomic.Xadd(&sched.nmspinning, 1)
			}
			goto top
		}
	} else if pollUntil != 0 && netpollinited() {
		pollerPollUntil := int64(atomic.Load64(&sched.pollUntil))
		if pollerPollUntil == 0 || pollerPollUntil > pollUntil {
			netpollBreak()
		}
	}
	stopm()
	goto top
}

// 民意调查报告此P是否可以进行非背景工作。这是一个相当轻量级的检查，用于
// 后台工作循环，比如空闲GC。它检查
// 由实际调度程序检查的条件的子集。
func pollWork() bool {
	if sched.runqsize != 0 {
		return true
	}
	p := getg().m.p.ptr()
	if !runqempty(p) {
		return true
	}
	if netpollinited() && atomic.Load(&netpollWaiters) > 0 && sched.lastpoll != 0 {
		if list := netpoll(0); !list.empty() {
			injectglist(&list)
			return true
		}
	}
	return false
}

// stealWork试图从任何P窃取可运行的goroutine或计时器。
// 
// 如果newWork为真，则新工作可能已经准备好。
// 
// 如果现在不是0，则是当前时间。stealWork返回传递的时间或
// 如果现在传递为0，则返回当前时间。
func stealWork(now int64) (gp *g, inheritTime bool, rnow, pollUntil int64, newWork bool) {
	pp := getg().m.p.ptr()

	ranTimer := false

	const stealTries = 4
	for i := 0; i < stealTries; i++ {
		stealTimersOrRunNextG := i == stealTries-1

		for enum := stealOrder.start(fastrand()); !enum.done(); enum.next() {
			if sched.gcwaiting != 0 {
				// 可能有GC工作。
				return nil, false, now, pollUntil, true
			}
			p2 := allp[enum.position()]
			if pp == p2 {
				continue
			}

			// 从p2窃取计时器。这个对checkTimers的调用是唯一一个我们可以锁定不同P的计时器的地方。在检查runnext之前，我们在最后一次通过时做了一次，因为偷了
			// 从另一个P的runnext应该是最后的手段，所以如果有
			// 是计时器先偷。
			// 
			// 我们只检查其中一个偷取迭代的计时器，因为
			// 存储在now中的时间在这个循环中不会改变，并且使用now 
			// 相同的值多次检查每个P的计时器可能是浪费时间。
			// 
			// TIMERPSMASK告诉我们P是否可能有计时器。如果
			// 不能，根本不需要检查。
			if stealTimersOrRunNextG && timerpMask.read(enum.position()) {
				tnow, w, ran := checkTimers(p2, now)
				now = tnow
				if w != 0 && (pollUntil == 0 || w < pollUntil) {
					pollUntil = w
				}
				if ran {
					// 运行计时器可能会使
					// 准备好任意数量的G的
					// 并将它们添加到此P的
					// 本地运行队列。这使
					// 运行库
					// 的假设无效，即它总是有空间添加
					// 被盗的G。因此，现在检查是否有
					// 要运行的本地G。
					if gp, inheritTime := runqget(pp); gp != nil {
						return gp, inheritTime, now, pollUntil, ranTimer
					}
					ranTimer = true
				}
			}

			// 如果p2空闲，不要费心尝试偷窃。
			if !idlepMask.read(enum.position()) {
				if gp := runqsteal(pp, p2, stealTimersOrRunNextG); gp != nil {
					return gp, false, now, pollUntil, ranTimer
				}
			}
		}
	}

	// 未发现有盗贼。不管怎样，运行计时器可能会让
	// 准备好一些我们错过的goroutine。指示下一个计时器
	// 等待。
	return nil, false, now, pollUntil, ranTimer
}

// 检查所有Ps是否有可供窃取的可运行G。
// 
// 在输入时我们没有P。如果G可用于窃取，而P可用，
// 返回P，调用方应获取该P并尝试窃取
// 工作。
func checkRunqsNoP(allpSnapshot []*p, idlepMaskSnapshot pMask) *p {
	for id, p2 := range allpSnapshot {
		if !idlepMaskSnapshot.read(uint32(id)) && !runqempty(p2) {
			lock(&sched.lock)
			pp := pidleget()
			unlock(&sched.lock)
			if pp != nil {
				return pp
			}

			// 无法获取P，不要麻烦检查剩余的Ps。
			break
		}
	}

	return nil
}

// 检查所有Ps是否有一个比POLLINTOR更快过期的计时器。
// 
// 返回更新的pollUntil值。
func checkTimersNoP(allpSnapshot []*p, timerpMaskSnapshot pMask, pollUntil int64) int64 {
	for id, p2 := range allpSnapshot {
		if timerpMaskSnapshot.read(uint32(id)) {
			w := nobarrierWakeTime(p2)
			if w != 0 && (pollUntil == 0 || w < pollUntil) {
				pollUntil = w
			}
		}
	}

	return pollUntil
}

// 检查空闲优先级GC，输入时不带P。
// 
// 如果一些GC工作、一个P和一个工作者G都可用，则P和G将被返回。返回的P尚未连接。
func checkIdleGCNoP() (*p, *g) {
	// N.B.由于我们没有P，gcBlackenEnabled可能随时发生变化；我们
	// 在获得P后必须再次检查。
	if atomic.Load(&gcBlackenEnabled) == 0 {
		return nil, nil
	}
	if !gcMarkWorkAvailable(nil) {
		return nil, nil
	}

	// 工作可用；我们只能在有
	// 可用的P和可用的工作程序G的情况下启动一个空闲的GC工作程序。
	// 
	// 我们可以尝试以任意顺序获取它们，尽管两者都有
	// 同步问题（见下文）。工作人员几乎总是
	// 可用（参见FindRunnablegWorker中关于
	// 可能没有的一个案例的评论）。因为我们不太可能找到P，所以先检查一下。
	// 
	// 同步：请注意，我们必须保留sched。锁定直到我们
	// 承诺保留它。否则我们不能把不必要的P 
	// 放回sched。在不执行全套空闲
	// 转换检查的情况下进行pidle。
	// 
	// 如果我们要先检查gcBgMarkWorkerPool，我们必须以某种方式处理gcControllerState中的假设。findRunnableGCWorker，
	// 空的gcBgMarkWorkerPool只有在gcMarkDone运行时才可能出现。
	lock(&sched.lock)
	pp := pidleget()
	if pp == nil {
		unlock(&sched.lock)
		return nil, nil
	}

	// 现在我们拥有一个P，GCBlackEnabled无法更改（因为它需要
	// STW）。
	if gcBlackenEnabled == 0 {
		pidleput(pp)
		unlock(&sched.lock)
		return nil, nil
	}

	node := (*gcBgMarkWorkerNode)(gcBgMarkWorkerPool.pop())
	if node == nil {
		pidleput(pp)
		unlock(&sched.lock)
		return nil, nil
	}

	unlock(&sched.lock)

	return pp, node.gp.ptr()
}

// wakeNetPoller唤醒在网络轮询器中睡眠的线程，如果它不是
// 将在when参数之前唤醒；或者，它唤醒一个空闲的P来服务
// 计时器和网络轮询器（如果还没有）。
func wakeNetPoller(when int64) {
	if atomic.Load64(&sched.lastpoll) == 0 {
		// 在findrunnable中，我们确保在轮询pollUntil 
		// 字段时为零或当前
		// 轮询预计运行的时间。这可能会有一个虚假的唤醒
		// 但千万不要错过唤醒。
		pollerPollUntil := int64(atomic.Load64(&sched.pollUntil))
		if pollerPollUntil == 0 || pollerPollUntil > when {
			netpollBreak()
		}
	} else {
		// 网络轮询器中没有线程，请尝试在那里获取
		// 一个线程，以便它可以处理新的计时器。
		if GOOS != "plan9" { // 临时解决方案——见问题#42303。
			wakep()
		}
	}
}

func resetspinning() {
	_g_ := getg()
	if !_g_.m.spinning {
		throw("resetspinning: not a spinning m")
	}
	_g_.m.spinning = false
	nmspinning := atomic.Xadd(&sched.nmspinning, -1)
	if int32(nmspinning) < 0 {
		throw("findrunnable: negative nmspinning")
	}
	// M唤醒策略故意有些保守，所以请检查我们是否需要在这里唤醒另一个P。有关详细信息，请参阅文件顶部的“工作线程停止/取消连接”
	// 注释。
	wakep()
}

// injectglist将列表中的每个可运行的G添加到某个运行队列，
// 并清除glist。如果没有当前的P，它们将被添加到
// 全局队列中，最多NPM将开始运行它们。
// 否则，对于每个空闲的P，这会将一个G添加到全局队列
// 并启动一个M。任何剩余的G都会添加到当前P的
// 本地运行队列。
// 这可能会暂时获得sched。锁
// 可以与GC并行运行。
func injectglist(glist *gList) {
	if glist.empty() {
		return
	}
	if trace.enabled {
		for gp := glist.head.ptr(); gp != nil; gp = gp.schedlink.ptr() {
			traceGoUnpark(gp, 0)
		}
	}

	// 将所有goroutines标记为可运行，然后再将它们放到运行队列中。
	head := glist.head.ptr()
	var tail *g
	qsize := 0
	for gp := head; gp != nil; gp = gp.schedlink.ptr() {
		tail = gp
		qsize++
		casgstatus(gp, _Gwaiting, _Grunnable)
	}

	// 把英语变成一个队列。
	var q gQueue
	q.head.set(head)
	q.tail.set(tail)
	*glist = gList{}

	startIdle := func(n int) {
		for ; n != 0 && sched.npidle != 0; n-- {
			startm(nil, false)
		}
	}

	pp := getg().m.p.ptr()
	if pp == nil {
		lock(&sched.lock)
		globrunqputbatch(&q, int32(qsize))
		unlock(&sched.lock)
		startIdle(qsize)
		return
	}

	npidle := int(atomic.Load(&sched.npidle))
	var globq gQueue
	var n int
	for n = 0; n < npidle && !q.empty(); n++ {
		g := q.pop()
		globq.pushBack(g)
	}
	if n > 0 {
		lock(&sched.lock)
		globrunqputbatch(&globq, int32(n))
		unlock(&sched.lock)
		startIdle(n)
		qsize -= n
	}

	if !q.empty() {
		runqputbatch(pp, &q, qsize)
	}
}

// 一轮调度程序：找到一个可运行的goroutine并执行它。
// 永远不会回来。
func schedule() {
	_g_ := getg()

	if _g_.m.locks != 0 {
		throw("schedule: holding locks")
	}

	if _g_.m.lockedg != 0 {
		stoplockedm()
		execute(_g_.m.lockedg.ptr(), false) // 永远不会回来。
	}

	// 由于cgo调用使用的是m的g0堆栈，所以我们不应该将执行cgo调用的g调度到别处。
	if _g_.m.incgo {
		throw("schedule: in cgo")
	}

top:
	pp := _g_.m.p.ptr()
	pp.preempt = false

	if sched.gcwaiting != 0 {
		gcstopm()
		goto top
	}
	if pp.runSafePointFn != 0 {
		runSafePointFn()
	}

	// 正常检查：如果我们正在旋转，运行队列应该是空的。
	// 在调用checktimer之前检查此项，因为这可能会调用
	// goready将就绪的goroutine放入本地运行队列。
	if _g_.m.spinning && (pp.runnext != 0 || pp.runqhead != pp.runqtail) {
		throw("schedule: spinning with local work")
	}

	checkTimers(pp, 0)

	var gp *g
	var inheritTime bool

	// 正常的goroutines将检查是否需要唤醒准备就绪，
	// 但GCworkers和TraceReader不会，因此必须在此处进行检查。
	tryWakeP := false
	if trace.enabled || trace.shutdown {
		gp = traceReader()
		if gp != nil {
			casgstatus(gp, _Gwaiting, _Grunnable)
			traceGoUnpark(gp, 0)
			tryWakeP = true
		}
	}
	if gp == nil && gcBlackenEnabled != 0 {
		gp = gcController.findRunnableGCWorker(_g_.m.p.ptr())
		if gp != nil {
			tryWakeP = true
		}
	}
	if gp == nil {
		// 偶尔检查一下全局可运行队列以确保公平性。
		// 否则，两个Goroutine可以通过不断重新启动来完全占据本地运行队列。
		if _g_.m.p.ptr().schedtick%61 == 0 && sched.runqsize > 0 {
			lock(&sched.lock)
			gp = globrunqget(_g_.m.p.ptr(), 1)
			unlock(&sched.lock)
		}
	}
	if gp == nil {
		gp, inheritTime = runqget(_g_.m.p.ptr())
		// 我们可以看到gp！=这里为零，即使M正在旋转，
		// 如果checkTimers通过goready添加了本地goroutine。
	}
	if gp == nil {
		gp, inheritTime = findrunnable() // 阻塞直到工作可用
	}

	// 此线程将运行一个goroutine并且不再旋转，
	// 因此如果它被标记为旋转，我们需要立即重置它，并且可能
	// 启动一个新的旋转M。
	if _g_.m.spinning {
		resetspinning()
	}

	if sched.disable.user && !schedEnabled(gp) {
		// 此goroutine的调度被禁用。把它放到
		// 当我们
		// 重新启用用户调度并再次查看时，挂起的可运行goroutines列表上。
		lock(&sched.lock)
		if schedEnabled(gp) {
			// 当我们获取锁时，重新启用了调度。
			unlock(&sched.lock)
		} else {
			sched.disable.runnable.pushBack(gp)
			sched.disable.n++
			unlock(&sched.lock)
			goto top
		}
	}

	// 如果要安排一个不正常的goroutine（GCworker或tracereader），
	// 如果有，唤醒一个P。
	if tryWakeP {
		wakep()
	}
	if gp.lockedm != 0 {
		// 将自己的p交给锁定的m，
		// 然后阻止等待新p。
		startlockedm(gp)
		goto top
	}

	execute(gp, inheritTime)
}

// dropg删除m和当前goroutine->curg（简称gp）之间的关联。
// 通常，调用者会将gp的状态设置为远离Grunning，然后
// 立即调用dropg来完成任务。呼叫方还负责安排在适当的时间使用ready重新启动gp。在调用dropg并安排gp为
// 稍后准备好后，调用方可以做其他工作，但最终应该调用schedule来重新启动此m上的goroutines调度。
func dropg() {
	_g_ := getg()

	setMNoWB(&_g_.m.curg.m, nil)
	setGNoWB(&_g_.m.curg, nil)
}

// checkTimers为P运行任何准备就绪的计时器。
// 如果现在不是0，则是当前时间。
// 如果现在传递为0，则返回传递的时间或当前时间。
// 以及下一个计时器应该运行的时间，如果没有下一个计时器，则为0，并报告是否运行了任何计时器。
// 如果下一个计时器应该运行的时间不是0，
// 它总是大于返回的时间。
// 我们现在来回传递，以避免额外的纳米时间呼叫。
// go:yeswritebarrierrec 
func checkTimers(pp *p, now int64) (rnow, pollUntil int64, ran bool) {
	// 如果第一个计时器或第一个调整的
	// 计时器还没到时间，那就没什么可做的了。
	next := int64(atomic.Load64(&pp.timer0When))
	nextAdj := int64(atomic.Load64(&pp.timerModifiedEarliest))
	if next == 0 || (nextAdj != 0 && nextAdj < next) {
		next = nextAdj
	}

	if next == 0 {
		// 无需运行或调整计时器。
		return now, 0, false
	}

	if now == 0 {
		now = nanotime()
	}
	if now < next {
		// 下一个计时器尚未准备好运行，但请继续运行
		// 如果我们要清除已删除的计时器。
		// 这对应于下面的条件，其中
		// 我们决定是否调用ClearDeletedTimer。
		if pp != getg().m.p.ptr() || int(atomic.Load(&pp.deletedTimers)) <= int(atomic.Load(&pp.numTimers)/4) {
			return now, next, false
		}
	}

	lock(&pp.timersLock)

	if len(pp.timers) > 0 {
		adjusttimers(pp, now)
		for len(pp.timers) > 0 {
			// 注意runtimer可能会临时解锁
			// pp.timersLock。
			if tw := runtimer(pp, now); tw != 0 {
				if tw > 0 {
					pollUntil = tw
				}
				break
			}
			ran = true
		}
	}

	// 如果这是本地P，并且有很多已删除的计时器，
	// 清除它们。我们只对本地P这样做是为了减少
	// timersLock上的锁争用。
	if pp == getg().m.p.ptr() && int(atomic.Load(&pp.deletedTimers)) > len(pp.timers)/4 {
		clearDeletedTimers(pp)
	}

	unlock(&pp.timersLock)

	return now, pollUntil, ran
}

func parkunlock_c(gp *g, lock unsafe.Pointer) bool {
	unlock((*mutex)(lock))
	return true
}

// g0继续停车。
func park_m(gp *g) {
	_g_ := getg()

	if trace.enabled {
		traceGoPark(_g_.m.waittraceev, _g_.m.waittraceskip)
	}

	casgstatus(gp, _Grunning, _Gwaiting)
	dropg()

	if fn := _g_.m.waitunlockf; fn != nil {
		ok := fn(gp, _g_.m.waitlock)
		_g_.m.waitunlockf = nil
		_g_.m.waitlock = nil
		if !ok {
			if trace.enabled {
				traceGoUnpark(gp, 2)
			}
			casgstatus(gp, _Gwaiting, _Grunnable)
			execute(gp, true) // 将其重新安排，永不返回。
		}
	}
	schedule()
}

func goschedImpl(gp *g) {
	status := readgstatus(gp)
	if status&^_Gscan != _Grunning {
		dumpgstatus(gp)
		throw("bad g status")
	}
	casgstatus(gp, _Grunning, _Grunnable)
	dropg()
	lock(&sched.lock)
	globrunqput(gp)
	unlock(&sched.lock)

	schedule()
}

// 在g0上继续。
func gosched_m(gp *g) {
	if trace.enabled {
		traceGoSched()
	}
	goschedImpl(gp)
}

// goschedguarded是一个禁止状态避免版本的gosched_m 
func goschedguarded_m(gp *g) {

	if !canPreemptM(gp.m) {
		gogo(&gp.sched) // 永不返回
	}

	if trace.enabled {
		traceGoSched()
	}
	goschedImpl(gp)
}

func gopreempt_m(gp *g) {
	if trace.enabled {
		traceGoPreempt()
	}
	goschedImpl(gp)
}

// preemptPark parks gp并将其放入Gpreempted。
// 
// go:systemstack 
func preemptPark(gp *g) {
	if trace.enabled {
		traceGoPark(traceEvGoBlock, 0)
	}
	status := readgstatus(gp)
	if status&^_Gscan != _Grunning {
		dumpgstatus(gp)
		throw("bad g status")
	}
	gp.waitreason = waitReasonPreempted

	if gp.asyncSafePoint {
		// 仔细检查异步抢占是否在SPWRITE汇编函数中发生。
		// isAsyncSafePoint必须排除此情况。
		f := findfunc(gp.sched.pc)
		if !f.valid() {
			throw("preempt at unknown pc")
		}
		if f.flag&funcFlag_SPWRITE != 0 {
			println("runtime: unexpected SPWRITE function", funcname(f), "in async preempt")
			throw("preempt SPWRITE")
		}
	}

	// 从_Grunning到_Gscan |u gp的过渡被取消。我们不能在我们放弃的时候，因为那样的话，我们就可以在没有M的情况下运行
	// 但是在我们被释放的那一刻，
	// 在我们完全清理之前，有什么东西可以声称这个G。因此，我们将扫描位设置为进一步锁定
	// 转换，直到我们可以删除。
	casGToPreemptScan(gp, _Grunning, _Gscan|_Gpreempted)
	dropg()
	casfrom_Gscanstatus(gp, _Gscan|_Gpreempted, _Gpreempted)
	schedule()
}

// GOYILD与Gosched类似，但它：
// -发出GoPreempt跟踪事件，而不是Gosched跟踪事件
// -将当前G置于当前P的runq上，而不是全局runq 
func goyield() {
	checkTimeouts()
	mcall(goyield_m)
}

func goyield_m(gp *g) {
	if trace.enabled {
		traceGoPreempt()
	}
	pp := gp.m.p.ptr()
	casgstatus(gp, _Grunning, _Grunnable)
	dropg()
	runqput(pp, gp, false)
	schedule()
}

// 完成当前GOROUTE的执行。goexit继续g0。
func goexit1() {
	if raceenabled {
		racegoend()
	}
	if trace.enabled {
		traceGoEnd()
	}
	mcall(goexit0)
}

func goexit0(gp *g) {
	_g_ := getg()
	_p_ := _g_.m.p.ptr()

	casgstatus(gp, _Grunning, _Gdead)
	gcController.addScannableStack(_p_, -int64(gp.stack.hi-gp.stack.lo))
	if isSystemGoroutine(gp, false) {
		atomic.Xadd(&sched.ngsys, -1)
	}
	gp.m = nil
	locked := gp.lockedm != 0
	gp.lockedm = 0
	_g_.m.lockedg = 0
	gp.preemptStop = false
	gp.paniconfault = false
	gp._defer = nil // 应该是真的，但以防万一。
	gp._panic = nil // 在恐慌期间，Goexit非零。指向堆栈分配的数据。
	gp.writebuf = nil
	gp.waitreason = 0
	gp.param = nil
	gp.labels = nil
	gp.timer = nil

	if gcBlackenEnabled != 0 && gp.gcAssistBytes > 0 {
		// 向全球池提供冲水协助。如果应用程序是
		// 快速创建一个退出的goroutines，这将为起搏提供更好的信息。
		assistWorkPerByte := gcController.assistWorkPerByte.Load()
		scanCredit := int64(assistWorkPerByte * float64(gp.gcAssistBytes))
		atomic.Xaddint64(&gcController.bgScanCredit, scanCredit)
		gp.gcAssistBytes = 0
	}

	dropg()

	if GOARCH == "wasm" { // wasm上还没有线程
		gfput(_p_, gp)
		schedule() // 永远不会返回
	}

	if _g_.m.lockedInt != 0 {
		print("invalid m->lockedInt = ", _g_.m.lockedInt, "\n")
		throw("internal lockOSThread error")
	}
	gfput(_p_, gp)
	if locked {
		// goroutine可能已锁定此线程，因为
		// 它将其置于异常内核状态。杀死它
		// 而不是将其返回到线程池。

		// 返回mstart，它将释放P并退出线程。
		if GOOS != "plan9" { // 见golang。org/issue/22227。
			gogo(&_g_.m.g0.sched)
		} else {
			// 清除plan9上的LockedText，因为我们可能会重新使用
			// 此线程。
			_g_.m.lockedExt = 0
		}
	}
	schedule()
}

// 保存更新getg（）。计划引用pc和sp，以便下面的
// gogo将恢复pc和sp。
// 
// save不能有写障碍，因为调用写障碍
// 可以关闭getg（）。附表。
// 
// go:nosplit 
// go:nowritebarrierrec 
func save(pc, sp uintptr) {
	_g_ := getg()

	if _g_ == _g_.m.g0 || _g_ == _g_.m.gsignal {
		// m.g0。sched是特殊的，必须描述退出线程的上下文
		// 。mstart1直接向其写入。
		// m.gsignal。sched完全不应该被使用。
		// 此检查确保save调用不会意外地在写入系统g的上下文中运行。
		throw("save on system g not allowed")
	}

	_g_.sched.pc = pc
	_g_.sched.sp = sp
	_g_.sched.lr = 0
	_g_.sched.ret = 0
	// 我们需要确保ctxt为零，但这里不能有写
	// 障碍。然而，它应该一直是零。
	// 断言。
	if _g_.sched.ctxt != nil {
		badctxt()
	}
}

// goroutine即将进入系统调用。
// 记录下它不再使用cpu。wen jian defg
// 
// Entersyscall无法拆分堆栈：保存必须
// 使g->sched引用调用方的堆栈段，因为
// Entersyscall将立即返回。
// 
// entersyscall调用也不能拆分堆栈。
// 在对syscall、
// 进行活动调用期间，我们无法安全地移动堆栈，因为我们不知道哪个uintptr参数是真正的指针（返回堆栈）。
// 在实践中，这意味着我们让快速路径通过
// entersycall运行，而慢速路径必须使用systemstack 
// 在系统堆栈上运行更大的东西。
// 
// reentersyscall是cgo回调使用的入口点，其中显式地
// 还原保存的SP和PC。当exitsyscall将从调用堆栈中比父级更高的函数调用
// 时，这是必需的，因为g->syscallsp 
// 必须始终指向有效的堆栈帧。下面的entersyscall是syscalls的正常
// 入口点，它从调用者那里获取SP和PC。
// 
// 系统调用跟踪：
// 在系统调用开始时，我们发出traceGoSysCall以捕获堆栈跟踪。
// 如果系统调用没有阻塞，也就是说，我们不会发出任何其他事件。
// 如果系统调用阻塞（即P被重新捕获），重新捕获器将发出traceGoSysBlock；
// 当syscall返回时，我们发出traceGoSysExit，当goroutine开始运行
// （如果exitsyscallfast返回true，可能会立即发出）时，我们发出traceGoStart。
// 为了确保traceGoSysExit严格在traceGoSysBlock之后发出，
// 我们记住了syscalltick的当前值，单位为m（_g_u.m.syscalltick=_g.m.p.ptr（）。syscalltick），
// 任何发出traceGoSysBlock的人都会在之后增加p.syscalltick；
// 我们在发出traceGoSysExit之前等待增量。ABCDEGG
// 
// go:nosplit 
func reentersyscall(pc, sp uintptr) {
	_g_ := getg()

	// 禁用抢占，因为在此函数期间，g处于Gsyscall状态，
	// 但可能有不一致的g->sched，不要让GC观察它。
	_g_.m.locks++

	// Entersyscall不能调用任何可能拆分/增长堆栈的函数。
	// /（详情见上述评论。）
	// 通过将堆栈保护替换为
	// 将触发任何堆栈检查并留下一个标志来通知newstack停止，从而捕获可能发生的调用。
	_g_.stackguard0 = stackPreempt
	_g_.throwsplit = true

	// 将SP留下进行GC和回溯。
	save(pc, sp)
	_g_.syscallsp = sp
	_g_.syscallpc = pc
	casgstatus(_g_, _Grunning, _Gsyscall)
	if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
		systemstack(func() {
			print("entersyscall inconsistent ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
			throw("entersyscall")
		})
	}

	if trace.enabled {
		systemstack(traceGoSysCall)
		// systemstack本身会破坏g.sched。{pc，sp}我们以后可能需要它们，当G在一个
		// 系统调用
		save(pc, sp)
	}

	if atomic.Load(&sched.sysmonwait) != 0 {
		systemstack(entersyscall_sysmon)
		save(pc, sp)
	}

	if _g_.m.p.ptr().runSafePointFn != 0 {
		// 如果在这个堆栈上运行
		systemstack(runSafePointFn)
		save(pc, sp)
	}

	_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
	_g_.sysblocktraced = true
	pp := _g_.m.p.ptr()
	pp.m = 0
	_g_.m.oldp.set(pp)
	_g_.m.p = 0
	atomic.Store(&pp.status, _Psyscall)
	if sched.gcwaiting != 0 {
		systemstack(entersyscall_gcwait)
		save(pc, sp)
	}

	_g_.m.locks--
}

// go系统调用库和正常cgo调用使用的标准系统调用条目时，我们可能需要它们。
// 
// 这是通过linkname导出到syscall包中的程序集的。
// 
// go:nosplit 
// go:linkname entersyscall 
func entersyscall() {
	reentersyscall(getcallerpc(), getcallersp())
}

func entersyscall_sysmon() {
	lock(&sched.lock)
	if atomic.Load(&sched.sysmonwait) != 0 {
		atomic.Store(&sched.sysmonwait, 0)
		notewakeup(&sched.sysmonnote)
	}
	unlock(&sched.lock)
}

func entersyscall_gcwait() {
	_g_ := getg()
	_p_ := _g_.m.oldp.ptr()

	lock(&sched.lock)
	if sched.stopwait > 0 && atomic.Cas(&_p_.status, _Psyscall, _Pgcstop) {
		if trace.enabled {
			traceGoSysBlock(_p_)
			traceProcStop(_p_)
		}
		_p_.syscalltick++
		if sched.stopwait--; sched.stopwait == 0 {
			notewakeup(&sched.stopnote)
		}
	}
	unlock(&sched.lock)
}

// 与entersyscall（）相同，但提示系统调用正在阻塞。
// go:nosplit 
func entersyscallblock() {
	_g_ := getg()

	_g_.m.locks++ // 请参阅entersyscall中的注释
	_g_.throwsplit = true
	_g_.stackguard0 = stackPreempt // 请参阅entersyscall中的注释
	_g_.m.syscalltick = _g_.m.p.ptr().syscalltick
	_g_.sysblocktraced = true
	_g_.m.p.ptr().syscalltick++

	// 将SP留给GC和回溯。
	pc := getcallerpc()
	sp := getcallersp()
	save(pc, sp)
	_g_.syscallsp = _g_.sched.sp
	_g_.syscallpc = _g_.sched.pc
	if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
		sp1 := sp
		sp2 := _g_.sched.sp
		sp3 := _g_.syscallsp
		systemstack(func() {
			print("entersyscallblock inconsistent ", hex(sp1), " ", hex(sp2), " ", hex(sp3), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
			throw("entersyscallblock")
		})
	}
	casgstatus(_g_, _Grunning, _Gsyscall)
	if _g_.syscallsp < _g_.stack.lo || _g_.stack.hi < _g_.syscallsp {
		systemstack(func() {
			print("entersyscallblock inconsistent ", hex(sp), " ", hex(_g_.sched.sp), " ", hex(_g_.syscallsp), " [", hex(_g_.stack.lo), ",", hex(_g_.stack.hi), "]\n")
			throw("entersyscallblock")
		})
	}

	systemstack(entersyscallblock_handoff)

	// 在阻塞呼叫期间重新保存以进行回溯。
	save(getcallerpc(), getcallersp())

	_g_.m.locks--
}

func entersyscallblock_handoff() {
	if trace.enabled {
		traceGoSysCall()
		traceGoSysBlock(getg().m.p.ptr())
	}
	handoffp(releasep())
}

// goroutine g退出了系统调用。
// 安排它再次在cpu上运行。
// 这只从go syscall库调用，而不是从运行时使用的低级系统调用调用。
// 
// 由于我们的P可能已被盗，因此不允许使用写屏障。
// 
// 这是通过linkname导出到syscall包中的程序集的。
// 
// go:nosplit 
// go:nowritebarrierrec 
// go:linkname exitsyscall 
func exitsyscall() {
	_g_ := getg()

	_g_.m.locks++ // 请参阅entersyscall中的注释
	if getcallersp() > _g_.syscallsp {
		throw("exitsyscall: syscall frame is no longer valid")
	}

	_g_.waitsince = 0
	oldp := _g_.m.oldp.ptr()
	_g_.m.oldp = 0
	if exitsyscallfast(oldp) {
		if trace.enabled {
			if oldp != _g_.m.p.ptr() || _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
				systemstack(traceGoStart)
			}
		}
		// 我们有一个cpu，所以我们可以运行。
		_g_.m.p.ptr().syscalltick++
		// 在恢复之前，我们需要确认状态并扫描。。。
		casgstatus(_g_, _Gsyscall, _Grunning)

		// 垃圾收集器没有运行（因为我们正在运行），
		// 所以可以清除syscallsp。
		_g_.syscallsp = 0
		_g_.m.locks--
		if _g_.preempt {
			// 如果我们在newstack中清除了抢占请求，则恢复抢占请求。否则，恢复真正的_StackGuard，我们在entersyscall/entersyscallblock中破坏了它。
			_g_.stackguard0 = stackPreempt
		} else {
			_g_.stackguard0 = _g_.stack.lo + _StackGuard
		}
		_g_.throwsplit = false

		if sched.disable.user && !schedEnabled(_g_) {
			// 此goroutine的调度被禁用。
			Gosched()
		}

		return
	}

	_g_.sysexitticks = 0
	if trace.enabled {
		// 等待traceGoSysBlock事件发出。
		// 这确保了跟踪的一致性（goroutine在被阻止后启动）。
		for oldp != nil && oldp.syscalltick == _g_.m.syscalltick {
			osyield()
		}
		// 我们现在无法跟踪系统调用退出，因为我们没有P。
		// 跟踪代码可以调用写屏障，没有P就无法运行。
		// 因此，我们记住系统调用退出时间，并在有P时发出事件
		// in execute。
		_g_.sysexitticks = cputicks()
	}

	_g_.m.locks--

	// 调用调度程序。
	mcall(exitsyscall0)

	// 调度程序已返回，因此我们现在可以运行了。
	// 删除系统调用期间留给垃圾收集器的syscallsp信息。
	// 必须等到现在，因为直到gosched返回
	// 我们不能确定垃圾收集器
	// 是否正在运行。
	_g_.syscallsp = 0
	_g_.m.p.ptr().syscalltick++
	_g_.throwsplit = false
}

// go:nosplit 
func exitsyscallfast(oldp *p) bool {
	_g_ := getg()

	// 冻结世界设置停止等待但不重新获取P。
	if sched.stopwait == freezeStopWait {
		return false
	}

	// 尝试重新获取最后一个P。
	if oldp != nil && oldp.status == _Psyscall && atomic.Cas(&oldp.status, _Psyscall, _Pidle) {
		// 有一个cpu给我们，所以我们可以运行。
		wirep(oldp)
		exitsyscallfast_reacquired()
		return true
	}

	// 尝试获取任何其他空闲P。
	if sched.pidle != 0 {
		var ok bool
		systemstack(func() {
			ok = exitsyscallfast_pidle()
			if ok && trace.enabled {
				if oldp != nil {
					// 等待traceGoSysBlock事件发出。
					// 这确保了跟踪的一致性（goroutine在被阻止后启动）。
					for oldp.syscalltick == _g_.m.syscalltick {
						osyield()
					}
				}
				traceGoSysExit(0)
			}
		})
		if ok {
			return true
		}
	}
	return false
}

// exitsyscallfast_reacquired是此G 
// 已成功重新获取其在
// 系统调用之前运行的P的exitsyscall路径。
// 
// go:nosplit 
func exitsyscallfast_reacquired() {
	_g_ := getg()
	if _g_.m.syscalltick != _g_.m.p.ptr().syscalltick {
		if trace.enabled {
			// 重新获取p，然后再次进入syscall（因为_g.m.syscalltick已更改）。
			// 已经发出了此系统调用的traceGoSysBlock，
			// 但是在这里，我们实际上从运行在同一个p上的新系统调用中重新获取了p。
			systemstack(func() {
				// 表示阻止新的系统调用。
				traceGoSysBlock(_g_.m.p.ptr())
				// 表示当前系统调用的完成。
				traceGoSysExit(0)
			})
		}
		_g_.m.p.ptr().syscalltick++
	}
}

func exitsyscallfast_pidle() bool {
	lock(&sched.lock)
	_p_ := pidleget()
	if _p_ != nil && atomic.Load(&sched.sysmonwait) != 0 {
		atomic.Store(&sched.sysmonwait, 0)
		notewakeup(&sched.sysmonnote)
	}
	unlock(&sched.lock)
	if _p_ != nil {
		acquirep(_p_)
		return true
	}
	return false
}

// exitsyscall g0上的慢路径。
// 无法获取P，将gp列为可运行。
// 
// 通过mcall调用，因此gp是来自此M的调用g。
// 
// go:Nowriteerrec 
func exitsyscall0(gp *g) {
	casgstatus(gp, _Gsyscall, _Grunnable)
	dropg()
	lock(&sched.lock)
	var _p_ *p
	if schedEnabled(gp) {
		_p_ = pidleget()
	}
	var locked bool
	if _p_ == nil {
		globrunqput(gp)

		// 下面，如果gp被锁定，我们将停止锁定EDM。globrunqput释放
		// gp的所有权，因此我们必须在
		// 通过解锁sched提交释放之前检查gp是否已锁定。锁定，否则我们
		// 可能会与另一个正在从解锁状态转换为
		// 锁定状态的M进行竞争。永远不会回来。
		locked = gp.lockedm != 0
	} else if atomic.Load(&sched.sysmonwait) != 0 {
		atomic.Store(&sched.sysmonwait, 0)
		notewakeup(&sched.sysmonnote)
	}
	unlock(&sched.lock)
	if _p_ != nil {
		acquirep(_p_)
	}
	if locked {
		// 等待另一个线程再次调度gp和m。
		// 
		// N.B.locked必须是这个M，因为在entersycall之前，这个g在这个M上运行。永远不会回来。
		stoplockedm()
	}
	stopm()
	schedule() // 永远不会回来。
}

// 在fork之前从syscall包调用。
// go:linkname syscall_runtime_BeforeFork syscall。runtime_BeforeFork 
// go:nosplit 
func syscall_runtime_BeforeFork() {
	gp := getg().m.curg

	// 在fork期间阻塞信号，这样如果信号被发送到进程
	// 组，子进程就不会在exec之前运行信号处理程序。见第18600期。
	gp.m.locks++
	sigsave(&gp.m.sigmask)
	sigblock(false)

	// 此函数在syscall包中的fork之前调用。
	// fork和exec之间的代码不能分配内存，甚至不能尝试增加堆栈。
	// 在这里，我们破坏了g->_StackGuard，以可靠地检测任何试图增加堆栈的尝试。
	// runtime_AfterFork将在父进程中撤消此操作，但不会在子进程中撤消。huang jian du zhong
	gp.stackguard0 = stackFork
}

// go:linkname syscall_runtime_AfterFork syscall。runtime_AfterFork 
// go:nosplit 
func syscall_runtime_AfterFork() {
	gp := getg().m.curg

	// 查看beforefork中的注释。
	gp.stackguard0 = gp.stack.lo + _StackGuard

	msigrestore(gp.m.sigmask)

	gp.m.locks--
}

// inForkedChild在子进程中处理信号时为true。
// 在使用vWork的情况下，这用于避免调用libc函数。
var inForkedChild bool

// 在fork-in-child之后从syscall包调用。
// 将未忽略的信号重置为默认处理程序，
// 恢复信号掩码以准备执行。
// 
// 因为它可能在vWork期间被调用，因此可能是
// 临时与父进程共享地址空间，这必须
// 不要更改任何全局变量或调用可能会这样做的C代码。
// 
// go:linkname syscall_runtime_AfterForkInChild syscall。runtime_AfterForkInChild 
// go:nosplit 
// go:nowritebarrierrec 
func syscall_runtime_AfterForkInChild() {
	// 可以在这里更改全局变量inForkedChild 
	// 因为我们要将其更改回来。这里没有竞争，因为如果我们与父进程共享地址空间，那么父进程不能同时运行。
	inForkedChild = true

	clearSignalHandlers()

	// 当我们还是孩子的时候，我们是唯一运行的线程，
	// 所以我们知道没有其他改变。m、 西格马斯克。
	msigrestore(getg().m.sigmask)

	inForkedChild = false
}

// pendingPreemptSignals是已发送但未接收的抢占信号
// 的数量。这只适用于达尔文。
// For#41702。
var pendingPreemptSignals uint32

// 在Exec之前从syscall包调用。
// go:linkname syscall_runtime_BeforeExec syscall。运行时\u BeforeExec 
func syscall_runtime_BeforeExec() {
	// 在执行期间防止创建线程。
	execLock.lock()

	// 在达尔文，等待接收到
	// 的所有未决抢占信号。见第41702期。
	if GOOS == "darwin" || GOOS == "ios" {
		for int32(atomic.Load(&pendingPreemptSignals)) > 0 {
			osyield()
		}
	}
}

// 在Exec之后从syscall包调用。
// go:linkname syscall_runtime_AfterExec syscall。runtime_AfterExec 
func syscall_runtime_AfterExec() {
	execLock.unlock()
}

// 分配一个新的g，堆栈大小足以容纳stacksize字节。
func malg(stacksize int32) *g {
	newg := new(g)
	if stacksize >= 0 {
		stacksize = round2(_StackSystem + stacksize)
		systemstack(func() {
			newg.stack = stackalloc(uint32(stacksize))
		})
		newg.stackguard0 = newg.stack.lo + _StackGuard
		newg.stackguard1 = ^uintptr(0)
		// 清除堆栈底部的字。在ARM和ARM64上进行VDSO期间，我们在gsignal堆栈上记录了g 
		// 。
		*(*uintptr)(unsafe.Pointer(newg.stack.lo)) = 0
	}
	return newg
}

// 创建一个新的g运行fn。
// 把它放在g等待运行的队列中。
// 编译器将go语句转换为对该语句的调用。
func newproc(fn *funcval) {
	gp := getg()
	pc := getcallerpc()
	systemstack(func() {
		newg := newproc1(fn, gp, pc)

		_p_ := getg().m.p.ptr()
		runqput(_p_, newg, true)

		if mainStarted {
			wakep()
		}
	})
}

// 从fn开始创建一个新的g状态_Grunnable。callerpc是创建此函数的go语句的
// 地址。调用方负责
// 将新的g添加到调度程序中。
func newproc1(fn *funcval, callergp *g, callerpc uintptr) *g {
	_g_ := getg()

	if fn == nil {
		_g_.m.throwing = -1 // 不要转储完整堆栈
		throw("go of nil func value")
	}
	acquirem() // 禁用抢占，因为它可以在本地变量

	_p_ := _g_.m.p.ptr()
	newg := gfget(_p_)
	if newg == nil {
		newg = malg(_StackMin)
		casgstatus(newg, _Gidle, _Gdead)
		allgadd(newg) // 以g->状态Gdead发布，以便GC扫描程序不会查看未初始化的堆栈。
	}
	if newg.stack.hi == 0 {
		throw("newproc1: newg missing stack")
	}

	if readgstatus(newg) != _Gdead {
		throw("newproc1: new g is not Gdead")
	}

	totalSize := uintptr(4*goarch.PtrSize + sys.MinFrameSize) // 读取稍微超出帧
	totalSize = alignUp(totalSize, sys.StackAlign)
	sp := newg.stack.hi - totalSize
	spArg := sp
	if usesLR {
		// 调用方的LR 
		*(*uintptr)(unsafe.Pointer(sp)) = 0
		prepGoExitFrame(sp)
		spArg += sys.MinFrameSize
	}

	memclrNoHeapPointers(unsafe.Pointer(&newg.sched), unsafe.Sizeof(newg.sched))
	newg.sched.sp = sp
	newg.stktopsp = sp
	newg.sched.pc = abi.FuncPCABI0(goexit) + sys.PCQuantum // /+PCQuantum时的额外空间，以便之前的指令位于同一函数
	newg.sched.g = guintptr(unsafe.Pointer(newg))
	gostartcallfn(&newg.sched, fn)
	newg.gopc = callerpc
	newg.ancestors = saveAncestors(callergp)
	newg.startpc = fn.fn
	if isSystemGoroutine(newg, false) {
		atomic.Xadd(&sched.ngsys, +1)
	} else {
		// 只有用户goroutines继承pprof标签。
		if _g_.m.curg != nil {
			newg.labels = _g_.m.curg.labels
		}
	}
	// 跟踪初始过渡？
	newg.trackingSeq = uint8(fastrand())
	if newg.trackingSeq%gTrackingPeriod == 0 {
		newg.tracking = true
	}
	casgstatus(newg, _Gdead, _Grunnable)
	gcController.addScannableStack(_p_, int64(newg.stack.hi-newg.stack.lo))

	if _p_.goidcache == _p_.goidcacheend {
		// Sched。goidgen是最后分配的id，
		// 此批必须是[sched.goidgen+1，sched.goidgen+GoidCacheBatch]。
		// 在启动计划中。goidgen=0，所以主goroutine接收goid=1。
		_p_.goidcache = atomic.Xadd64(&sched.goidgen, _GoidCacheBatch)
		_p_.goidcache -= _GoidCacheBatch - 1
		_p_.goidcacheend = _p_.goidcache + _GoidCacheBatch
	}
	newg.goid = int64(_p_.goidcache)
	_p_.goidcache++
	if raceenabled {
		newg.racectx = racegostart(callerpc)
	}
	if trace.enabled {
		traceGoCreate(newg, newg.startpc)
	}
	releasem(_g_.m)

	return newg
}

// 保存祖先复制给定调用方g的以前祖先，
// 将当前调用方的信息包含到正在创建的一个g的一组新回溯中。
func saveAncestors(callergp *g) *[]ancestorInfo {
	// 复制所有以前的信息，除了根goroutine（goid 0）。
	if debug.tracebackancestors <= 0 || callergp.goid == 0 {
		return nil
	}
	var callerAncestors []ancestorInfo
	if callergp.ancestors != nil {
		callerAncestors = *callergp.ancestors
	}
	n := int32(len(callerAncestors)) + 1
	if n > debug.tracebackancestors {
		n = debug.tracebackancestors
	}
	ancestors := make([]ancestorInfo, n)
	copy(ancestors[1:], callerAncestors)

	var pcs [_TracebackMaxFrames]uintptr
	npcs := gcallers(callergp, 0, pcs[:])
	ipcs := make([]uintptr, npcs)
	copy(ipcs, pcs[:])
	ancestors[0] = ancestorInfo{
		pcs:  ipcs,
		goid: callergp.goid,
		gopc: callergp.gopc,
	}

	ancestorsp := new([]ancestorInfo)
	*ancestorsp = ancestors
	return ancestorsp
}

// 放在gfree列表上。
// 如果本地列表太长，请将一个批转移到全局列表。
func gfput(_p_ *p, gp *g) {
	if readgstatus(gp) != _Gdead {
		throw("gfput: bad status (not Gdead)")
	}

	stksize := gp.stack.hi - gp.stack.lo

	if stksize != _FixedStack {
		// 非标准堆栈大小-释放它。
		stackfree(gp.stack)
		gp.stack.lo = 0
		gp.stack.hi = 0
		gp.stackguard0 = 0
	}

	_p_.gFree.push(gp)
	_p_.gFree.n++
	if _p_.gFree.n >= 64 {
		var (
			inc      int32
			stackQ   gQueue
			noStackQ gQueue
		)
		for _p_.gFree.n >= 32 {
			gp = _p_.gFree.pop()
			_p_.gFree.n--
			if gp.stack.lo == 0 {
				noStackQ.push(gp)
			} else {
				stackQ.push(gp)
			}
			inc++
		}
		lock(&sched.gFree.lock)
		sched.gFree.noStack.pushAll(noStackQ)
		sched.gFree.stack.pushAll(stackQ)
		sched.gFree.n += inc
		unlock(&sched.gFree.lock)
	}
}

// 从gfree列表中获取。
// 如果本地列表为空，则从全局列表中获取一批。
func gfget(_p_ *p) *g {
retry:
	if _p_.gFree.empty() && (!sched.gFree.stack.empty() || !sched.gFree.noStack.empty()) {
		lock(&sched.gFree.lock)
		// 将一批免费Gs移动到P。
		for _p_.gFree.n < 32 {
			// 更喜欢带堆栈的Gs。
			gp := sched.gFree.stack.pop()
			if gp == nil {
				gp = sched.gFree.noStack.pop()
				if gp == nil {
					break
				}
			}
			sched.gFree.n--
			_p_.gFree.push(gp)
			_p_.gFree.n++
		}
		unlock(&sched.gFree.lock)
		goto retry
	}
	gp := _p_.gFree.pop()
	if gp == nil {
		return nil
	}
	_p_.gFree.n--
	if gp.stack.lo == 0 {
		// 堆栈已在gfput中解除分配。分配一个新的。
		systemstack(func() {
			gp.stack = stackalloc(_FixedStack)
		})
		gp.stackguard0 = gp.stack.lo + _StackGuard
	} else {
		if raceenabled {
			racemalloc(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
		}
		if msanenabled {
			msanmalloc(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
		}
		if asanenabled {
			asanunpoison(unsafe.Pointer(gp.stack.lo), gp.stack.hi-gp.stack.lo)
		}
	}
	return gp
}

// 将所有缓存的G从gfree列表清除到全局列表。
func gfpurge(_p_ *p) {
	var (
		inc      int32
		stackQ   gQueue
		noStackQ gQueue
	)
	for !_p_.gFree.empty() {
		gp := _p_.gFree.pop()
		_p_.gFree.n--
		if gp.stack.lo == 0 {
			noStackQ.push(gp)
		} else {
			stackQ.push(gp)
		}
		inc++
	}
	lock(&sched.gFree.lock)
	sched.gFree.noStack.pushAll(noStackQ)
	sched.gFree.stack.pushAll(stackQ)
	sched.gFree.n += inc
	unlock(&sched.gFree.lock)
}

// 断点执行断点陷阱。
func Breakpoint() {
	breakpoint()
}

// 修改m.locked后，LockOSThread和下面的LockOSThread调用dolockOSThread。在这个调用过程中不允许抢占，
// 否则m在这个函数中可能与在调用方中不同。
// go:nosplit 
func dolockOSThread() {
	if GOARCH == "wasm" {
		return // wasm上还没有线程
	}
	_g_ := getg()
	_g_.m.lockedg.set(_g_)
	_g_.lockedm.set(_g_.m)
}

// go:nosplit 

// LockOSThread将调用goroutine连接到其当前操作系统线程。
// 调用的goroutine将始终在该线程中执行，
// 并且在调用goroutine对
// 的调用次数与对LockOSThread的调用次数相同之前，其他goroutine不会在该线程中执行。
// 如果调用goroutine退出而没有解锁线程，
// 线程将被终止。
// 
// 所有初始化函数都在启动线程上运行。从init函数调用LockOSThread 
// 将导致在该线程上调用main函数。
// 
// goroutine应该在调用OS服务或
// 依赖于每个线程状态的非Go库函数之前调用LockOSThread。
func LockOSThread() {
	if atomic.Load(&newmHandoff.haveTemplateThread) == 0 && GOOS != "plan9" {
		// 如果我们需要从锁定的
		// 线程启动一个新线程，我们需要模板线程。现在就开始
		// 当我们处于已知良好状态时。
		startTemplateThread()
	}
	_g_ := getg()
	_g_.m.lockedExt++
	if _g_.m.lockedExt == 0 {
		_g_.m.lockedExt--
		panic("LockOSThread nesting overflow")
	}
	dolockOSThread()
}

// go:nosplit 
func lockOSThread() {
	getg().m.lockedInt++
	dolockOSThread()
}

// dounlockOSThread由UnlockOSThread调用，并且在更新m->locked之后，在
// 下面的UnlockOSThread。在调用过程中不允许抢占，
// 否则m在该函数中可能与在调用方中不同。
// go:nosplit 
func dounlockOSThread() {
	if GOARCH == "wasm" {
		return // wasm上还没有线程
	}
	_g_ := getg()
	if _g_.m.lockedInt != 0 || _g_.m.lockedExt != 0 {
		return
	}
	_g_.m.lockedg = 0
	_g_.lockedm = 0
}

// go:nosplit 

// UnlockOSThread撤消先前对LockOSThread的调用。
// 如果此操作将调用goroutine的
// 上的活动LockOSThread调用数减为零，则将从其固定的操作系统线程
// 上取消调用goroutine。
// 如果没有活动的LockOSThread调用，这是不可操作的。
// 
// 在调用UnlockOSThread之前，调用方必须确保OS 
// 线程适合运行其他goroutine。如果调用者对线程的状态做了任何永久性的更改，这将影响其他goroutine，那么它不应该调用这个函数，从而将goroutine锁定到操作系统线程，直到goroutine（和
// /因此线程）退出。
func UnlockOSThread() {
	_g_ := getg()
	if _g_.m.lockedExt == 0 {
		return
	}
	_g_.m.lockedExt--
	dounlockOSThread()
}

// go:nosplit 
func unlockOSThread() {
	_g_ := getg()
	if _g_.m.lockedInt == 0 {
		systemstack(badunlockosthread)
	}
	_g_.m.lockedInt--
	dounlockOSThread()
}

func badunlockosthread() {
	throw("runtime: internal error: misuse of lockOSThread/unlockOSThread")
}

func gcount() int32 {
	n := int32(atomic.Loaduintptr(&allglen)) - sched.gFree.n - int32(atomic.Load(&sched.ngsys))
	for _, _p_ := range allp {
		n -= _p_.gFree.n
	}

	// 所有这些变量都可以同时更改，因此结果可能不一致。
	// 但至少当前的goroutine正在运行。
	if n < 1 {
		n = 1
	}
	return n
}

func mcount() int32 {
	return int32(sched.mnext - sched.nmfreed)
}

var prof struct {
	signalLock uint32
	hz         int32
}

func _System()                    { _System() }
func _ExternalCode()              { _ExternalCode() }
func _LostExternalCode()          { _LostExternalCode() }
func _GC()                        { _GC() }
func _LostSIGPROFDuringAtomic64() { _LostSIGPROFDuringAtomic64() }
func _VDSO()                      { _VDSO() }

// 如果我们收到SIGPROF信号，就会呼叫。
// 由信号处理程序调用，可能在STW期间运行。
// go:nowritebarrierrec 
func sigprof(pc, sp, lr uintptr, gp *g, mp *m) {
	if prof.hz == 0 {
		return
	}

	// /如果议员。profilehz为0，则未为此线程启用分析。
	// 我们必须检查此项，以避免setcpuprofilerate 
	// 和对cpuprof的调用之间出现死锁。添加，如下。
	if mp != nil && mp.profilehz == 0 {
		return
	}

	// 在mips{，le}/arm上，64位原子在
	// 在关键部分，它创建了一个死锁（在编写示例时）。
	// 作为一种解决方法，在关键部分
	// 创建一个sigprof计数器来存储计数，并将其传递给sigprof。稍后当SIGPROF是
	// 运行时/内部/原子中使用自旋锁进行仿真。如果SIGPROF在程序在
	// 从其他地方接收时添加（）（将_LostSIGPROFDuringAtomic64作为pc）。
	if GOARCH == "mips" || GOARCH == "mipsle" || GOARCH == "arm" {
		if f := findfunc(pc); f.valid() {
			if hasPrefix(funcname(f), "runtime/internal/atomic") {
				cpuprof.lostAtomic++
				return
			}
		}
		if GOARCH == "arm" && goarm < 7 && GOOS == "linux" && pc&0xffff0000 == 0xffff0000 {
			// 运行时/内部/原子函数调用内核
			// arm<7上的帮助程序。请参阅
			// runtime/internal/atomic/sys_linux_arm。s、 
			cpuprof.lostAtomic++
			return
		}
	}

	// 评测与GC同时运行，因此它不能分配。
	// 设置陷阱，以防代码分配错误。
	// 请注意，在windows上，一个线程会获取所有
	// 其他线程的配置文件，因此mp通常不是getg（）。m、 
	// 事实上，mp甚至可能不会停止。
	// 见戈朗。org/issue/17165。
	getg().m.mallocing++

	var stk [maxCPUProfStack]uintptr
	n := 0
	if mp.ncgo > 0 && mp.curg != nil && mp.curg.syscallpc != 0 && mp.curg.syscallsp != 0 {
		cgoOff := 0
		// 检查cgocalers使用以确保我们不是
		// 中断其他正在摆弄
		// cgocalers的代码。我们在信号处理器
		// 中运行，所有信号都被阻塞，所以我们不必担心任何其他代码会干扰我们。
		if atomic.Load(&mp.cgoCallersUse) == 0 && mp.cgoCallers != nil && mp.cgoCallers[0] != 0 {
			for cgoOff < len(mp.cgoCallers) && mp.cgoCallers[cgoOff] != 0 {
				cgoOff++
			}
			copy(stk[:], mp.cgoCallers[:cgoOff])
			mp.cgoCallers[0] = 0
		}

		// 收集导致cgo调用的Go堆栈。
		n = gentraceback(mp.curg.syscallpc, mp.curg.syscallsp, 0, mp.curg, 0, &stk[cgoOff], len(stk)-cgoOff, nil, nil, 0)
		if n > 0 {
			n += cgoOff
		}
	} else {
		n = gentraceback(pc, sp, lr, gp, 0, &stk[0], len(stk), nil, nil, _TraceTrap|_TraceJumpStack)
	}

	if n <= 0 {
		// 无法正常回溯或回溯失败。
		// 看看它是否属于几种常见情况。
		n = 0
		if usesLibcall() && mp.libcallg != 0 && mp.libcallpc != 0 && mp.libcallsp != 0 {
			// Libcall，即windows上的运行时系统调用。
			// 收集导致调用的Go堆栈。
			n = gentraceback(mp.libcallpc, mp.libcallsp, 0, mp.libcallg.ptr(), 0, &stk[0], len(stk), nil, nil, 0)
		}
		if n == 0 && mp != nil && mp.vdsoSP != 0 {
			n = gentraceback(mp.vdsoPC, mp.vdsoSP, 0, gp, 0, &stk[0], len(stk), nil, nil, _TraceTrap|_TraceJumpStack)
		}
		if n == 0 {
			// 如果以上所有操作都失败，请根据抽象的“系统”或“GC”进行说明。
			n = 2
			if inVDSOPage(pc) {
				pc = abi.FuncPCABIInternal(_VDSO) + sys.PCQuantum
			} else if pc > firstmoduledata.etext {
				// /“外部代码”比“etext”好。
				pc = abi.FuncPCABIInternal(_ExternalCode) + sys.PCQuantum
			}
			stk[0] = pc
			if mp.preemptoff != "" {
				stk[1] = abi.FuncPCABIInternal(_GC) + sys.PCQuantum
			} else {
				stk[1] = abi.FuncPCABIInternal(_System) + sys.PCQuantum
			}
		}
	}

	if prof.hz != 0 {
		// 注意：在Windows上可能会发生这样的情况：我们在没有g的情况下中断了系统线程
		// 因此gp可能为零。其他零检查是根据
		// 警告进行的，但在实践中预计不会为零。
		var tagPtr *unsafe.Pointer
		if gp != nil && gp.m != nil && gp.m.curg != nil {
			tagPtr = &gp.m.curg.labels
		}
		cpuprof.add(tagPtr, stk[:n])
	}
	getg().m.mallocing--
}

// setcpuprofilerate将CPU分析速率设置为每秒hz次。
// 如果hz<=0，则SetCpupFileRate将关闭CPU评测。
func setcpuprofilerate(hz int32) {
	// 强迫理智的论点。
	if hz < 0 {
		hz = 0
	}

	// 禁用抢占，否则我们可以重新调度到另一个启用了评测的线程
	// 上。
	_g_ := getg()
	_g_.m.locks++

	// 停止此线程上的探查器，以便安全地锁定prof.
	// 如果在我们锁定prof时出现分析信号，
	// 它将死锁。
	setThreadCPUProfiler(0)

	for !atomic.Cas(&prof.signalLock, 0, 1) {
		osyield()
	}
	if prof.hz != hz {
		setProcessCPUProfiler(hz)
		prof.hz = hz
	}
	atomic.Store(&prof.signalLock, 0)

	lock(&sched.lock)
	sched.profilehz = hz
	unlock(&sched.lock)

	if hz != 0 {
		setThreadCPUProfiler(hz)
	}

	_g_.m.locks--
}

// 初始化pp，它可能是新分配的p或
// 以前销毁的p，并将其转换为状态_Pgcstop。
func (pp *p) init(id int32) {
	pp.id = id
	pp.status = _Pgcstop
	pp.sudogcache = pp.sudogbuf[:0]
	pp.deferpool = pp.deferpoolbuf[:0]
	pp.wbBuf.reset()
	if pp.mcache == nil {
		if id == 0 {
			if mcache0 == nil {
				throw("missing mcache?")
			}
			// 使用引导mcache0。只有一个P将获得
			// mcache0:ID为0的那个。
			pp.mcache = mcache0
		} else {
			pp.mcache = allocmcache()
		}
	}
	if raceenabled && pp.raceprocctx == 0 {
		if id == 0 {
			pp.raceprocctx = raceprocctx0
			raceprocctx0 = 0 // 引导
		} else {
			pp.raceprocctx = raceproccreate()
		}
	}
	lockInit(&pp.timersLock, lockRankTimers)

	// 此P在开始运行时可能会得到计时器。在这里设置掩码
	// 因为P可能不会通过pidleget（尤其是启动时的P 0）。
	timerpMask.set(id)
	// 类似地，我们可能不会在P启动前通过pidleget 
	// 运行，如果启动时是P 0。
	idlepMask.clear(id)
}

// 销毁释放与pp和
// 
// sched。锁必须被锁住，世界必须被阻止。
func (pp *p) destroy() {
	assertLockHeld(&sched.lock)
	assertWorldStopped()

	// 将所有可运行的Goroutine移动到全局队列
	for pp.runqhead != pp.runqtail {
		// 从本地队列尾部弹出
		pp.runqtail--
		gp := pp.runq[pp.runqtail%uint32(len(pp.runq))].ptr()
		// 推到全局队列的头部
		globrunqputhead(gp)
	}
	if pp.runnext != 0 {
		globrunqputhead(pp.runnext.ptr())
		pp.runnext = 0
	}
	if len(pp.timers) > 0 {
		plocal := getg().m.p.ptr()
		// 世界停止了，但我们获得了
		// 防止系统调用timeSleepUntil。
		// 这是我们持有
		// 多个P的时间锁的唯一情况，因此不存在死锁问题。
		lock(&plocal.timersLock)
		lock(&pp.timersLock)
		moveTimers(plocal, pp.timers)
		pp.timers = nil
		pp.numTimers = 0
		pp.deletedTimers = 0
		atomic.Store64(&pp.timer0When, 0)
		unlock(&pp.timersLock)
		unlock(&plocal.timersLock)
	}
	// 刷新p的写屏障缓冲区。
	if gcphase != _GCoff {
		wbBufFlush1(pp)
		pp.gcw.dispose()
	}
	for i := range pp.sudogbuf {
		pp.sudogbuf[i] = nil
	}
	pp.sudogcache = pp.sudogbuf[:0]
	for j := range pp.deferpoolbuf {
		pp.deferpoolbuf[j] = nil
	}
	pp.deferpool = pp.deferpoolbuf[:0]
	systemstack(func() {
		for i := 0; i < pp.mspancache.len; i++ {
			// 既然世界停止了，打电话是安全的。
			mheap_.spanalloc.free(unsafe.Pointer(pp.mspancache.buf[i]))
		}
		pp.mspancache.len = 0
		lock(&mheap_.lock)
		pp.pcache.flush(&mheap_.pages)
		unlock(&mheap_.lock)
	})
	freemcache(pp.mcache)
	pp.mcache = nil
	gfpurge(pp)
	traceProcFree(pp)
	if raceenabled {
		if pp.timerRaceCtx != 0 {
			// 竞赛检测器代码使用回调函数获取进程上下文，因此请安排回调函数
			// 以查看正确的内容。
			// 这个黑客程序之所以有效，是因为我们是唯一一个正在运行的
			// 线程。
			mp := getg().m
			phold := mp.p.ptr()
			mp.p.set(pp)

			racectxend(pp.timerRaceCtx)
			pp.timerRaceCtx = 0

			mp.p.set(phold)
		}
		raceprocdestroy(pp.raceprocctx)
		pp.raceprocctx = 0
	}
	pp.gcAssistTime = 0
	pp.status = _Pdead
}

// 更改处理器数量。
// 
// sched。锁必须被锁住，世界必须被阻止。
// 
// gcworkbufs不能被GC或写屏障
// 代码修改，因此，如果Ps的数量实际发生变化，GC不能运行。
// 
// 返回具有本地工作的Ps列表，它们需要由调用方安排。
func procresize(nprocs int32) *p {
	assertLockHeld(&sched.lock)
	assertWorldStopped()

	old := gomaxprocs
	if old < 0 || nprocs <= 0 {
		throw("procresize: invalid arg")
	}
	if trace.enabled {
		traceGomaxprocs(nprocs)
	}

	// 更新统计数据
	now := nanotime()
	if sched.procresizetime != 0 {
		sched.totaltime += int64(old) * (now - sched.procresizetime)
	}
	sched.procresizetime = now

	maskWords := (nprocs + 31) / 32

	// 如有必要，增加allp。
	if nprocs > int32(len(allp)) {
		// 与重拍同步，重拍可能正在运行
		// 同时，因为它不在P上运行。
		lock(&allpLock)
		if nprocs <= int32(cap(allp)) {
			allp = allp[:nprocs]
		} else {
			nallp := make([]*p, nprocs)
			// 将所有内容复制到allp的上限，所以我们
			// 永远不会丢失旧的已分配Ps。
			copy(nallp, allp[:cap(allp)])
			allp = nallp
		}

		if maskWords <= int32(cap(idlepMask)) {
			idlepMask = idlepMask[:maskWords]
			timerpMask = timerpMask[:maskWords]
		} else {
			nidlepMask := make([]uint32, maskWords)
			// 无需在len之外复制，旧Ps是不相关的。
			copy(nidlepMask, idlepMask)
			idlepMask = nidlepMask

			ntimerpMask := make([]uint32, maskWords)
			copy(ntimerpMask, timerpMask)
			timerpMask = ntimerpMask
		}
		unlock(&allpLock)
	}

	// 初始化新P的
	for i := old; i < nprocs; i++ {
		pp := allp[i]
		if pp == nil {
			pp = new(p)
		}
		pp.init(i)
		atomicstorep(unsafe.Pointer(&allp[i]), unsafe.Pointer(pp))
	}

	_g_ := getg()
	if _g_.m.p != 0 && _g_.m.p.ptr().id < nprocs {
		// 继续使用当前P 
		_g_.m.p.ptr().status = _Prunning
		_g_.m.p.ptr().mcache.prepareForSweep()
	} else {
		// 释放当前P并获取所有P[0]。
		// 
		// 我们必须在销毁当前的P 
		// 因为P.destroy本身有写障碍，所以我们
		// 需要从有效的P.
		if _g_.m.p != 0 {
			if trace.enabled {
				// 假装我们被取消了计划
				// 然后再次计划保持
				// 跟踪正常。
				traceGoSched()
				traceProcStop(_g_.m.p.ptr())
			}
			_g_.m.p.ptr().m = 0
		}
		_g_.m.p = 0
		p := allp[0]
		p.m = 0
		p.status = _Pidle
		acquirep(p)
		if trace.enabled {
			traceGoStart()
		}
	}

	// g.m.p现在已设置，因此我们不再需要mcache0进行引导。
	mcache0 = nil

	// 从未使用的P的
	for i := nprocs; i < old; i++ {
		p := allp[i]
		p.destroy()
		// 释放资源无法释放P本身，因为它可以被系统调用
	}

	// Trim allp中的M引用。
	if int32(len(allp)) != nprocs {
		lock(&allpLock)
		allp = allp[:nprocs]
		idlepMask = idlepMask[:maskWords]
		timerpMask = timerpMask[:maskWords]
		unlock(&allpLock)
	}

	var runnablePs *p
	for i := nprocs - 1; i >= 0; i-- {
		p := allp[i]
		if _g_.m.p.ptr() == p {
			continue
		}
		p.status = _Pidle
		if runqempty(p) {
			pidleput(p)
		} else {
			p.m.set(mget())
			p.link.set(runnablePs)
			runnablePs = p
		}
	}
	stealOrder.reset(uint32(nprocs))
	var int32p *int32 = &gomaxprocs // 让编译器检查gomaxprocs是否为int32 
	atomic.Store((*uint32)(unsafe.Pointer(int32p)), uint32(nprocs))
	return runnablePs
}

// 将p与当前m关联。
// 
// 此函数允许有写障碍，即使调用方
// 不是，因为它立即获得了。
// 
// go:yeswritebarrierrec 
func acquirep(_p_ *p) {
	// 做不允许有写障碍的部分。
	wirep(_p_)

	// 有p；现在允许写障碍。

	// 在该P可以从可能过时的mcache分配
	// 之前，执行延迟的mcache刷新。
	_p_.mcache.prepareForSweep()

	if trace.enabled {
		traceProcStart()
	}
}

// wirep是acquirep的第一步，它实际上将
// 当前的M与_p!。这个问题被打破了，所以我们可以禁止为这部分写
// 障碍，因为我们还没有一个P。
// 
// go:nowritebarrierrec 
// go:nosplit 
func wirep(_p_ *p) {
	_g_ := getg()

	if _g_.m.p != 0 {
		throw("wirep: already in go")
	}
	if _p_.m != 0 || _p_.status != _Pidle {
		id := int64(0)
		if _p_.m != 0 {
			id = _p_.m.ptr().id
		}
		print("wirep: p->m=", _p_.m, "(", id, ") p->status=", _p_.status, "\n")
		throw("wirep: invalid p state")
	}
	_g_.m.p.set(_p_)
	_p_.m.set(_g_.m)
	_p_.status = _Prunning
}

// 解除P和当前m的关联。
func releasep() *p {
	_g_ := getg()

	if _g_.m.p == 0 {
		throw("releasep: invalid arg")
	}
	_p_ := _g_.m.p.ptr()
	if _p_.m.ptr() != _g_.m || _p_.status != _Prunning {
		print("releasep: m=", _g_.m, " m->p=", _g_.m.p.ptr(), " p->m=", hex(_p_.m), " p->status=", _p_.status, "\n")
		throw("releasep: invalid p state")
	}
	if trace.enabled {
		traceProcStop(_g_.m.p.ptr())
	}
	_g_.m.p = 0
	_p_.m = 0
	_p_.status = _Pidle
	return _p_
}

func incidlelocked(v int32) {
	lock(&sched.lock)
	sched.nmidlelocked += v
	if v > 0 {
		checkdead()
	}
	unlock(&sched.lock)
}

// 检查死锁情况。
// 检查基于运行M的数量，如果0->死锁。
// sched。锁必须锁好。
func checkdead() {
	assertLockHeld(&sched.lock)

	// 对于-buildmode=c-shared或-buildmode=c-archive，如果没有正在运行的goroutine，则可以。调用程序为
	// 假定正在运行。
	if islibrary || isarchive {
		return
	}

	// 如果我们因为在一个已经空闲的线程上捕捉到信号而死亡，
	// 冻结世界将导致所有正在运行的线程阻塞。
	// 运行时将基本上进入死锁状态，除了有一个线程将很快调用exit。
	if panicking > 0 {
		return
	}

	// 如果我们不是在cgo下运行，但我们有一个额外的M，那么请为其设置
	// 帐户。（在没有cgo的情况下，Windows上可能会有一个额外的M到
	// 以适应syscall.NewCallback创建的回调。有关详细信息，请参阅问题#6751 
	// 。）
	var run0 int32
	if !iscgo && cgoHasExtraM {
		mp := lockextra(true)
		haveExtraM := extraMCount > 0
		unlockextra(mp)
		if haveExtraM {
			run0 = 1
		}
	}

	run := mcount() - sched.nmidle - sched.nmidlelocked - sched.nmsys
	if run > run0 {
		return
	}
	if run < 0 {
		print("runtime: checkdead: nmidle=", sched.nmidle, " nmidlelocked=", sched.nmidlelocked, " mcount=", mcount(), " nmsys=", sched.nmsys, "\n")
		throw("checkdead: inconsistent counts")
	}

	grunning := 0
	forEachG(func(gp *g) {
		if isSystemGoroutine(gp, false) {
			return
		}
		s := readgstatus(gp)
		switch s &^ _Gscan {
		case _Gwaiting,
			_Gpreempted:
			grunning++
		case _Grunnable,
			_Grunning,
			_Gsyscall:
			print("runtime: checkdead: find g ", gp.goid, " in status ", s, "\n")
			throw("checkdead: runnable g")
		}
	})
	if grunning == 0 { // 如果主goroutine调用runtime·Goexit（）
		unlock(&sched.lock) // 解锁，使GODEBUG=scheddetail=1不挂起
		throw("no goroutines (main called runtime.Goexit) - deadlock!")
	}

	// 可能会提前跳转到游乐场。
	if faketime != 0 {
		when, _p_ := timeSleepUntil()
		if _p_ != nil {
			faketime = when
			for pp := &sched.pidle; *pp != 0; pp = &(*pp).ptr().link {
				if (*pp).ptr() == _p_ {
					*pp = _p_.link
					break
				}
			}
			mp := mget()
			if mp == nil {
				// 应该始终有一个空闲的M，因为
				// 没有运行任何东西。
				throw("checkdead: no m for timer")
			}
			mp.nextp.set(_p_)
			notewakeup(&mp.park)
			return
		}
	}

	// 没有goroutines在运行，所以我们可以看看P。
	for _, _p_ := range allp {
		if len(_p_.timers) > 0 {
			return
		}
	}

	getg().m.throwing = -1 // 不要转储满堆
	unlock(&sched.lock)    // 解锁以便GODEBUG=scheddeail=1不挂起
	throw("all goroutines are asleep - deadlock!")
}

// forcegcperiod是垃圾收集之间的最长时间，以纳秒为单位
// 收集。如果我们这么长时间没有垃圾收集，一个
// 将被迫运行。
// 
// 这是一个用于测试的变量。它通常不会改变。
var forcegcperiod int64 = 2 * 60 * 1e9

// 如果
// golang的解决方案为真，则NeedSysmon解决方案为真。在NetBSD上需要org/issue/42515。
var needSysmonWorkaround bool = false

// 总是在没有P的情况下运行，因此不允许使用写屏障。
// 
// go:nowritebarrierrec 
func sysmon() {
	lock(&sched.lock)
	sched.nmsys++
	checkdead()
	unlock(&sched.lock)

	lasttrace := int64(0)
	idle := 0 // 我们连续几个周期没有叫醒某人
	delay := uint32(0)

	for {
		if idle == 0 { // 从20秒睡眠开始。。。
			delay = 20
		} else if idle > 50 { // 1毫秒后开始加倍睡眠。。。
			delay *= 2
		}
		if delay > 10*1000 { // 最长10毫秒
			delay = 10 * 1000
		}
		usleep(delay)

		// 如果启用了schedtrace，则sysmon不应进入深度睡眠状态，以便
		// 它可以在正确的时间打印该信息。
		// 
		// 如果有任何活动的P，它也不应该进入深度睡眠状态，因为
		// 它可以从系统调用中重新获取P，抢占长时间运行的G，如果所有P都长时间忙碌，则轮询网络。
		// 
		// 如果任何P因为
		// 退出系统调用或因计时器过期而醒来，以便它
		// 可以继续执行这些任务。如果它从一个系统调用中唤醒，它将重置idle和delay作为赌注，因为它以前从一个
		// 系统调用中重新获取了一个P，所以在
		// 应用程序重新开始工作后不久，它可能需要再次执行该操作。当从计时器中唤醒
		// 时，它不会重置空闲状态，以避免给大部分时间处于睡眠状态的应用程序增加系统负载。
		now := nanotime()
		if debug.schedtrace <= 0 && (sched.gcwaiting != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs)) {
			lock(&sched.lock)
			if atomic.Load(&sched.gcwaiting) != 0 || atomic.Load(&sched.npidle) == uint32(gomaxprocs) {
				syscallWake := false
				next, _ := timeSleepUntil()
				if next > now {
					atomic.Store(&sched.sysmonwait, 1)
					unlock(&sched.lock)
					// 使唤醒时间足够小，以确保采样正确。
					sleep := forcegcperiod / 2
					if next-now < sleep {
						sleep = next - now
					}
					shouldRelax := sleep >= osRelaxMinNS
					if shouldRelax {
						osRelax(true)
					}
					syscallWake = notetsleep(&sched.sysmonnote, sleep)
					if shouldRelax {
						osRelax(false)
					}
					lock(&sched.lock)
					atomic.Store(&sched.sysmonwait, 0)
					noteclear(&sched.sysmonnote)
				}
				if syscallWake {
					idle = 0
					delay = 20
				}
			}
			unlock(&sched.lock)
		}

		lock(&sched.sysmonlock)
		// 立即更新，以防我们在sysmonnote上阻塞或在上面的schedlock或sysmonlock上阻塞了很长时间。
		now = nanotime()

		// 如果需要，触发libc拦截器
		if *cgo_yield != nil {
			asmcgocall(*cgo_yield, nil)
		}
		// 如果轮询时间超过10毫秒，则轮询网络
		lastpoll := int64(atomic.Load64(&sched.lastpoll))
		if netpollinited() && lastpoll != 0 && lastpoll+10*1000*1000 < now {
			atomic.Cas64(&sched.lastpoll, uint64(lastpoll), uint64(now))
			list := netpoll(0) // 非阻塞-返回goroutines列表
			if !list.empty() {
				// 在注入glist之前，需要减少空闲锁定的M的
				// （假装还有一个正在运行）。
				// 否则可能会导致以下情况：
				// injectglist获取所有P，但在开始运行M之前，
				// 另一个M从syscall返回，完成运行其G，
				// 观察到没有工作要做，也没有其他运行M的
				// 并报告死锁。
				incidlelocked(-1)
				injectglist(&list)
				incidlelocked(1)
			}
		}
		if GOOS == "netbsd" && needSysmonWorkaround {
			// netpoll负责等待计时器
			// 过期，因此我们通常不必担心启动M to服务计时器。（请注意，上面的sleep for timesleeppuntil只是确保系统名
			// 在计时器过期时重新开始运行
			// 导致Go代码再次运行）。
			// 
			// 然而，netbsd有一个内核错误，有时
			// 会错过netpollBreak唤醒，这可能会导致
			// 服务计时器的无限延迟。如果我们检测到这个
			// 溢出，那么startm将获得一些东西来处理
			// 计时器。
			// 
			// 参见第42515期和
			// https:
			if next, _ := timeSleepUntil(); next < now {
				startm(nil, false)
			}
		}
		if atomic.Load(&scavenge.sysmonWake) != 0 {
			// 如果有人要求，将清道夫踢醒。
			wakeScavenger()
		}
		// 在系统调用中重新获取P的阻塞
		// 并抢占长时间运行的G的
		if retake(now) != 0 {
			idle = 0
		} else {
			idle++
		}
		// 检查是否需要强制执行GC 
		if t := (gcTrigger{kind: gcTriggerTime, now: now}); t.test() && atomic.Load(&forcegc.idle) != 0 {
			lock(&forcegc.lock)
			forcegc.idle = 0
			var list gList
			list.push(forcegc.g)
			injectglist(&list)
			unlock(&forcegc.lock)
		}
		if debug.schedtrace > 0 && lasttrace+int64(debug.schedtrace)*1000000 <= now {
			lasttrace = now
			schedtrace(debug.scheddetail > 0)
		}
		unlock(&sched.sysmonlock)
	}
}

type sysmontick struct {
	schedtick   uint32
	schedwhen   int64
	syscalltick uint32
	syscallwhen int64
}

// forcePreemptNS是在G被
// 抢占之前给G的时间片。
const forcePreemptNS = 10 * 1000 * 1000 // 10ms 

func retake(now int64) uint32 {
	n := 0
	// 防止所有P片更改。除非我们已经阻止了世界，否则这个锁将完全没有竞争。
	lock(&allpLock)
	// 我们不能在allp上使用范围循环，因为我们可能会暂时放弃allpLock。因此，我们需要在循环中每次重新获取
	// allp。
	for i := 0; i < len(allp); i++ {
		_p_ := allp[i]
		if _p_ == nil {
			// 如果procresize增长了
			// allp但尚未创建新的Ps，则可能发生这种情况。
			continue
		}
		pd := &_p_.sysmontick
		s := _p_.status
		sysretake := false
		if s == _Prunning || s == _Psyscall {
			// 如果运行时间过长，则抢占G。
			t := int64(_p_.schedtick)
			if int64(pd.schedtick) != t {
				pd.schedtick = uint32(t)
				pd.schedwhen = now
			} else if pd.schedwhen+forcePreemptNS <= now {
				preemptone(_p_)
				// 在系统调用的情况下，preemptone（）不起作用，因为没有连接到P的M。
				sysretake = true
			}
		}
		if s == _Psyscall {
			// 如果P在系统调用中超过1个系统周期（至少20秒），则从系统调用中重新获取P。
			t := int64(_p_.syscalltick)
			if !sysretake && int64(pd.syscalltick) != t {
				pd.syscalltick = uint32(t)
				pd.syscallwhen = now
				continue
			}
			// 一方面，如果没有其他工作要做，我们不想重新获取Ps，但另一方面，我们希望最终重新获取Ps，因为它们可以防止sysmon线程深度睡眠。
			if runqempty(_p_) && atomic.Load(&sched.nmspinning)+atomic.Load(&sched.npidle) > 0 && pd.syscallwhen+10*1000*1000 > now {
				continue
			}
			// 放下所有的按钮，我们就可以按计划行事了。锁
			unlock(&allpLock)
			// 需要在CAS前减少空闲锁定M的
			// （假装还有一个正在运行）。
			// 否则我们从中重新获取的M可以退出系统调用，
			// 增加nmidle并报告死锁。
			incidlelocked(-1)
			if atomic.Cas(&_p_.status, s, _Pidle) {
				if trace.enabled {
					traceGoSysBlock(_p_)
					traceProcStop(_p_)
				}
				n++
				_p_.syscalltick++
				handoffp(_p_)
			}
			incidlelocked(1)
			lock(&allpLock)
		}
	}
	unlock(&allpLock)
	return uint32(n)
}

// 告诉所有Goroutine，他们已经被抢先，应该停止。
// 此函数纯粹是最佳效果。如果
// 处理器刚刚开始运行，它可能无法通知goroutine。
// 不需要锁。
// 如果向至少一个goroutine发出抢占请求，则返回true。
func preemptall() bool {
	res := false
	for _, _p_ := range allp {
		if _p_.status != _Prunning {
			continue
		}
		if preemptone(_p_) {
			res = true
		}
	}
	return res
}

// 告诉在处理器P上运行的goroutine停止。
// 此函数纯粹是最佳效果。它可能会错误地未能通知
// goroutine。它可能会通知错误的goroutine。即使它通知
// 正确的goroutine，如果它是
// 同时执行newstack，goroutine可能会忽略该请求。
// 不需要锁。
// 如果发出抢占请求，则返回true。
// 实际的抢占将在未来的某个时间点发生
// 并将由gp->status note before 
// Grunning 
func preemptone(_p_ *p) bool {
	mp := _p_.m.ptr()
	if mp == nil || mp == getg().m {
		return false
	}
	gp := mp.curg
	if gp == nil || gp == mp.g0 {
		return false
	}

	gp.preempt = true

	// 通过
	// 将当前堆栈指针与gp->stackguard0进行比较来指示。
	// 将gp->stackguard0设置为StackPreempt折叠
	// 将preemption设置为正常堆栈溢出检查。
	gp.stackguard0 = stackPreempt

	// 请求对该P进行异步抢占。
	if preemptMSupported && debug.asyncpreemptoff == 0 {
		_p_.preempt = true
		preemptM(mp)
	}

	return true
}

var starttime int64

func schedtrace(detailed bool) {
	now := nanotime()
	if starttime == 0 {
		starttime = now
	}

	lock(&sched.lock)
	print("SCHED ", (now-starttime)/1e6, "ms: gomaxprocs=", gomaxprocs, " idleprocs=", sched.npidle, " threads=", mcount(), " spinningthreads=", sched.nmspinning, " idlethreads=", sched.nmidle, " runqueue=", sched.runqsize)
	if detailed {
		print(" gcwaiting=", sched.gcwaiting, " nmidlelocked=", sched.nmidlelocked, " stopwait=", sched.stopwait, " sysmonwait=", sched.sysmonwait, "\n")
	}
	// 从P、M和G读取数据时必须小心。
	// 即使我们持有schedlock，大多数数据也可以同时更改。
	// 例如（p->m？p->m->id:-1）如果p->m从非零变为零，可能会崩溃。
	for i, _p_ := range allp {
		mp := _p_.m.ptr()
		h := atomic.Load(&_p_.runqhead)
		t := atomic.Load(&_p_.runqtail)
		if detailed {
			id := int64(-1)
			if mp != nil {
				id = mp.id
			}
			print("  P", i, ": status=", _p_.status, " schedtick=", _p_.schedtick, " syscalltick=", _p_.syscalltick, " m=", id, " runqsize=", t-h, " gfreecnt=", _p_.gFree.n, " timerslen=", len(_p_.timers), "\n")
		} else {
			// 在非详细模式格式下，per-P运行队列的长度为：
			// /[len1 len2 len3 len4]
			print(" ")
			if i == 0 {
				print("[")
			}
			print(t - h)
			if i == len(allp)-1 {
				print("]\n")
			}
		}
	}

	if !detailed {
		unlock(&sched.lock)
		return
	}

	for mp := allm; mp != nil; mp = mp.alllink {
		_p_ := mp.p.ptr()
		gp := mp.curg
		lockedg := mp.lockedg.ptr()
		id1 := int32(-1)
		if _p_ != nil {
			id1 = _p_.id
		}
		id2 := int64(-1)
		if gp != nil {
			id2 = gp.goid
		}
		id3 := int64(-1)
		if lockedg != nil {
			id3 = lockedg.goid
		}
		print("  M", mp.id, ": p=", id1, " curg=", id2, " mallocing=", mp.mallocing, " throwing=", mp.throwing, " preemptoff=", mp.preemptoff, ""+" locks=", mp.locks, " dying=", mp.dying, " spinning=", mp.spinning, " blocked=", mp.blocked, " lockedg=", id3, "\n")
	}

	forEachG(func(gp *g) {
		mp := gp.m
		lockedm := gp.lockedm.ptr()
		id1 := int64(-1)
		if mp != nil {
			id1 = mp.id
		}
		id2 := int64(-1)
		if lockedm != nil {
			id2 = lockedm.id
		}
		print("  G", gp.goid, ": status=", readgstatus(gp), "(", gp.waitreason.String(), ") m=", id1, " lockedm=", id2, "\n")
	})
	unlock(&sched.lock)
}

// schedEnableUser启用或禁用用户
// goroutines的调度。
// 
// 这不会停止已经运行的用户goroutines，因此调用方
// 在禁用用户goroutines时应该首先停止world。
func schedEnableUser(enable bool) {
	lock(&sched.lock)
	if sched.disable.user == !enable {
		unlock(&sched.lock)
		return
	}
	sched.disable.user = !enable
	if enable {
		n := sched.disable.n
		sched.disable.n = 0
		globrunqputbatch(&sched.disable.runnable, n)
		unlock(&sched.lock)
		for ; n != 0 && sched.npidle != 0; n-- {
			startm(nil, false)
		}
	} else {
		unlock(&sched.lock)
	}
}

// 计划报告是否应安排gp。返回
// false为禁用gp调度。
// 
// sched。锁必须锁好。
func schedEnabled(gp *g) bool {
	assertLockHeld(&sched.lock)

	if sched.disable.user {
		return isSystemGoroutine(gp, true)
	}
	return true
}

// 将议员列入中间名单。
// sched。锁必须锁好。
// 可能在STW期间运行，因此不允许使用写屏障。
// 开始：nowritebarrierrec 
func mput(mp *m) {
	assertLockHeld(&sched.lock)

	mp.schedlink = sched.midle
	sched.midle.set(mp)
	sched.nmidle++
	checkdead()
}

// 尝试从中间列表中获得一个m。
// sched。锁必须锁好。
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func mget() *m {
	assertLockHeld(&sched.lock)

	mp := sched.midle.ptr()
	if mp != nil {
		sched.midle = mp.schedlink
		sched.nmidle--
	}
	return mp
}

// 将gp放入全局可运行队列。
// sched。锁必须锁好。
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func globrunqput(gp *g) {
	assertLockHeld(&sched.lock)

	sched.runq.pushBack(gp)
	sched.runqsize++
}

// 将gp置于全局可运行队列的首位。
// sched。锁必须锁好。
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func globrunqputhead(gp *g) {
	assertLockHeld(&sched.lock)

	sched.runq.push(gp)
	sched.runqsize++
}

// 将一批可运行的goroutines放入全局可运行队列。
// 这将清除*批。
// sched。锁必须锁好。
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func globrunqputbatch(batch *gQueue, n int32) {
	assertLockHeld(&sched.lock)

	sched.runq.pushBackAll(*batch)
	sched.runqsize += n
	*batch = gQueue{}
}

// 尝试从全局可运行队列中获取一批G。
// sched。锁必须锁好。
func globrunqget(_p_ *p, max int32) *g {
	assertLockHeld(&sched.lock)

	if sched.runqsize == 0 {
		return nil
	}

	n := sched.runqsize/gomaxprocs + 1
	if n > sched.runqsize {
		n = sched.runqsize
	}
	if max > 0 && n > max {
		n = max
	}
	if n > int32(len(_p_.runq))/2 {
		n = int32(len(_p_.runq)) / 2
	}

	sched.runqsize -= n

	gp := sched.runq.pop()
	n--
	for ; n > 0; n-- {
		gp1 := sched.runq.pop()
		runqput(_p_, gp1, false)
	}
	return gp
}

// pMask是一个原子位字符串，每个P有一位。如果设置了P id的位，则
type pMask []uint32

// read返回true。
func (p pMask) read(id uint32) bool {
	word := id / 32
	mask := uint32(1) << (id % 32)
	return (atomic.Load(&p[word]) & mask) != 0
}

// 设置P id的位。
func (p pMask) set(id int32) {
	word := id / 32
	mask := uint32(1) << (id % 32)
	atomic.Or(&p[word], mask)
}

// 清除P id的位。
func (p pMask) clear(id int32) {
	word := id / 32
	mask := uint32(1) << (id % 32)
	atomic.And(&p[word], ^mask)
}

// updateTimerPMask在其堆上没有计时器时清除pp的计时器掩码。
// 
// 理想情况下，在任何计时器
// 操作中，计时器掩码都会立即保持一致。不幸的是，更新
// 计时器热路径中的共享全局数据结构会在经常在无计时器和某些计时器之间切换
// 的应用程序中增加太多开销。
// 
// 作为一种折衷方案，计时器掩码仅在pidleget/pidleput上更新。运行P（由pidleget返回）的
// 可以随时添加计时器，因此必须设置其掩码
// 。当
// 空闲时，空闲的P（传递给pidleput）不能添加新的计时器，因此如果它当时没有计时器，它的掩码可能会被清除。
// 
// 因此，我们在findrunnable中获得了以下关于计时器窃取的效果：
// 
// /*空闲时没有计时器的空闲P永远不会在findrunnable 
// /（对于工作或计时器窃取，这是理想的情况）。
// /*必须始终检查正在运行的Ps。
// *计时器被盗的空闲Ps必须继续检查，直到再次运行
// ，即使在计时器过期后也是如此。
// 
// 当P重新开始运行时，应该设置掩码，因为可以随时添加计时器
// 。
// 
// TODO（prattmic）：额外的目标更新可能会改善上述情况。
// 例如，偷定时器时更新掩码。
func updateTimerPMask(pp *p) {
	if atomic.Load(&pp.numTimers) > 0 {
		return
	}

	// 看起来似乎没有计时器，但是另一个P可能会在处理
	// checkTimers中修改的计时器时暂时减少numtimer。我们必须用timersLock来序列化这些更改。
	lock(&pp.timersLock)
	if atomic.Load(&pp.numTimers) == 0 {
		timerpMask.clear(pp.id)
	}
	unlock(&pp.timersLock)
}

// pidleput将p to置于_Pidle列表中。
// 
// 这将在计划完成后释放p.的所有权。锁被释放它不再安全使用p.
// 
// sched。锁必须锁好。chuang jian defg
// 开始：nowritebarrierrec 
func pidleput(_p_ *p) {
	assertLockHeld(&sched.lock)

	if !runqempty(_p_) {
		throw("pidleput: P has non-empty run queue")
	}
	updateTimerPMask(_p_) // 如果没有计时器，请清除。
	idlepMask.set(_p_.id)
	_p_.link = sched.pidle
	sched.pidle.set(_p_)
	atomic.Xadd(&sched.npidle, 1) // TODO:快速原子
}

// pidleget试图从_Pidle列表中获取一个p，获得所有权。
// 
// sched。锁必须锁好。
// 
// 可能在STW期间运行，因此不允许使用写屏障。
// go:nowritebarrierrec 
func pidleget() *p {
	assertLockHeld(&sched.lock)

	_p_ := sched.pidle.ptr()
	if _p_ != nil {
		// 计时器现在可以随时添加。
		timerpMask.set(_p_.id)
		idlepMask.clear(_p_.id)
		sched.pidle = _p_.link
		atomic.Xadd(&sched.npidle, -1) // TODO:快速原子
	}
	return _p_
}

// runqempty报告p_u在其本地运行队列中是否没有Gs。
// 它永远不会错误地返回真值。
func runqempty(_p_ *p) bool {
	// 1）在runqnext中有G1，但runqhead==runqtail，
	// 2）runqput on将G1踢向runq，3）runqget on清空runqnext。
	// 只需观察runqhead==runqtail，然后观察runqnext==nil 
	// 并不意味着队列是空的。
	for {
		head := atomic.Load(&_p_.runqhead)
		tail := atomic.Load(&_p_.runqtail)
		runnext := atomic.Loaduintptr((*uintptr)(unsafe.Pointer(&_p_.runnext)))
		if tail == atomic.Load(&_p_.runqtail) {
			return head == tail && runnext == 0
		}
	}
}

// 为了摆脱关于调度顺序的潜在假设，
// 在使用种族检测器运行时，我们在调度决策中引入了一些随机性。通过改变Go 1.5中的
// （确定性）调度顺序，并打破
// 许多糟糕的书面测试，这一点的必要性变得显而易见。
// 鉴于这里的随机性，只要测试通过
// 与-race一致，他们就不应该有潜在的调度
// 假设。
const randomizeScheduler = raceenabled

// runqput试图将g放入本地可运行队列。
// 如果next为false，runqput会将g添加到可运行队列的尾部。
// 如果next为真，则runqput将g放入_p。运行下一个插槽。
// 如果运行队列已满，RUNEXT会将g放入全局队列。
// 仅由所有者执行P.
func runqput(_p_ *p, gp *g, next bool) {
	if randomizeScheduler && next && fastrandn(2) == 0 {
		next = false
	}

	if next {
	retryNext:
		oldnext := _p_.runnext
		if !_p_.runnext.cas(oldnext, guintptr(unsafe.Pointer(gp))) {
			goto retryNext
		}
		if oldnext == 0 {
			return
		}
		// 将旧跑步记录踢到常规跑步队列旁边。
		gp = oldnext.ptr()
	}

retry:
	h := atomic.LoadAcq(&_p_.runqhead) // load acquire，与消费者同步
	t := _p_.runqtail
	if t-h < uint32(len(_p_.runq)) {
		_p_.runq[t%uint32(len(_p_.runq))].set(gp)
		atomic.StoreRel(&_p_.runqtail, t+1) // store release，使商品可供消费
		return
	}
	if runqputslow(_p_, gp, h, t) {
		return
	}
	// 队列未满，现在上面的put必须成功
	goto retry
}

// put g和全局队列上本地可运行队列的一批工作。
// 仅由所有者执行。
func runqputslow(_p_ *p, gp *g, h, t uint32) bool {
	var batch [len(_p_.runq)/2 + 1]*g

	// 首先，从本地队列中获取一个批。
	n := t - h
	n = n / 2
	if n != uint32(len(_p_.runq)/2) {
		throw("runqputslow: queue is not full")
	}
	for i := uint32(0); i < n; i++ {
		batch[i] = _p_.runq[(h+i)%uint32(len(_p_.runq))].ptr()
	}
	if !atomic.CasRel(&_p_.runqhead, h, h+n) { // cas发布，提交和消费
		return false
	}
	batch[n] = gp

	if randomizeScheduler {
		for i := uint32(1); i <= n; i++ {
			j := fastrandn(i + 1)
			batch[i], batch[j] = batch[j], batch[i]
		}
	}

	// 链接goroutines。
	for i := uint32(0); i < n; i++ {
		batch[i].schedlink.set(batch[i+1])
	}
	var q gQueue
	q.head.set(batch[0])
	q.tail.set(batch[n])

	// 现在将该批放入全局队列。
	lock(&sched.lock)
	globrunqputbatch(&q, int32(n+1))
	unlock(&sched.lock)
	return true
}

// runqputbatch尝试将q上的所有G放入本地可运行队列。
// 如果队列已满，则将其放入全局队列；在这种情况下，
// 这将临时获取调度程序锁。
// 仅由所有者执行。
func runqputbatch(pp *p, q *gQueue, qsize int) {
	h := atomic.LoadAcq(&pp.runqhead)
	t := pp.runqtail
	n := uint32(0)
	for !q.empty() && t-h < uint32(len(pp.runq)) {
		gp := q.pop()
		pp.runq[t%uint32(len(pp.runq))].set(gp)
		t++
		n++
	}
	qsize -= int(n)

	if randomizeScheduler {
		off := func(o uint32) uint32 {
			return (pp.runqtail + o) % uint32(len(pp.runq))
		}
		for i := uint32(1); i < n; i++ {
			j := fastrandn(i + 1)
			pp.runq[off(i)], pp.runq[off(j)] = pp.runq[off(j)], pp.runq[off(i)]
		}
	}

	atomic.StoreRel(&pp.runqtail, t)
	if !q.empty() {
		lock(&sched.lock)
		globrunqputbatch(q, int32(qsize))
		unlock(&sched.lock)
	}
}

// 从本地可运行队列获取g。
// 如果inheritTime为true，则gp应继承
// 当前时间片中的剩余时间。否则，它应该开始一个新的时间片。
// 仅由所有者执行P.
func runqget(_p_ *p) (gp *g, inheritTime bool) {
	// 如果有runnext，则是下一个要运行的G。
	next := _p_.runnext
	// 如果runnext为非0且CAS失败，则它只能被另一个P窃取，因为其他P可以竞相将runnext设置为0，但只有当前P可以将其设置为非0。
	// 因此，如果此CAS失败，则无需重试。
	if next != 0 && _p_.runnext.cas(next, 0) {
		return next.ptr(), true
	}

	for {
		h := atomic.LoadAcq(&_p_.runqhead) // load acquire，与其他使用者同步
		t := _p_.runqtail
		if t == h {
			return nil, false
		}
		gp := _p_.runq[h%uint32(len(_p_.runq))].ptr()
		if atomic.CasRel(&_p_.runqhead, h, h+1) { // cas release，Commission consume 
			return gp, false
		}
	}
}

// runqdrain耗尽本地可运行队列_p_u，并返回其中的所有Goroutine。
// 仅由所有者执行P.
func runqdrain(_p_ *p) (drainQ gQueue, n uint32) {
	oldNext := _p_.runnext
	if oldNext != 0 && _p_.runnext.cas(oldNext, 0) {
		drainQ.pushBack(oldNext.ptr())
		n++
	}

retry:
	h := atomic.LoadAcq(&_p_.runqhead) // 加载获取，与其他使用者同步
	t := _p_.runqtail
	qn := t - h
	if qn == 0 {
		return
	}
	if qn > uint32(len(_p_.runq)) { // 读取不一致的h和t 
		goto retry
	}

	if !atomic.CasRel(&_p_.runqhead, h, h+qn) { // cas发布，提交消耗
		goto retry
	}

	// 我们颠倒了它从本地P的可运行队列
	// 中获取G的顺序，然后向前移动头指针，因为我们不想在runqdrain（）和runqsteal（）并行运行时弄乱G的
	// 的状态。
	// 因此，我们应该在将本地P排入gQueue之前提前头部指针，这样我们就可以更新任何gp。schedlink只有在我们获得G的全部所有权后，
	// 同时，其他P无法访问本地P可运行队列中的所有G并窃取它们。
	// 请参见https:
	for i := uint32(0); i < qn; i++ {
		gp := _p_.runq[(h+i)%uint32(len(_p_.runq))].ptr()
		drainQ.pushBack(gp)
		n++
	}
	return
}

// 将一批goroutines从_p_的可运行队列抓取到批中。
// Batch是从batchHead开始的环形缓冲区。
// 返回抓取的goroutine数。
// 可由任何P执行。
func runqgrab(_p_ *p, batch *[256]guintptr, batchHead uint32, stealRunNextG bool) uint32 {
	for {
		h := atomic.LoadAcq(&_p_.runqhead) // 加载获取，与其他消费者同步
		t := atomic.LoadAcq(&_p_.runqtail) // 加载获取，与生产者同步
		n := t - h
		n = n - n/2
		if n == 0 {
			if stealRunNextG {
				// 尝试从P_;。下一步。睡觉以确保_p_____________________________________________。
				if next := _p_.runnext; next != 0 {
					if _p_.status == _Prunning {
						// 这里的一个重要用例是，当运行
						// 的g在_p_u;ready（）上运行另一个g，然后几乎
						// 立即阻塞。不要在这个窗口中窃取runnext 
						// 而是退后一步，给u_u一个机会来安排runnext。这将避免在不同的Ps之间对gs 
						// 进行重击。
						// 自
						// 写入时起，同步chan发送/接收需要~50ns，因此3us会给出~50x的超调。
						if GOOS != "windows" {
							usleep(3)
						} else {
							// 在windows系统上，计时器粒度为
							// 1-15ms，这对于
							// 优化来说太多了。所以就让步吧。
							osyield()
						}
					}
					if !_p_.runnext.cas(next, 0) {
						continue
					}
					batch[batchHead%uint32(len(batch))] = next
					return 1
				}
			}
			return 0
		}
		if n > uint32(len(_p_.runq)/2) { // 读取不一致的h和t 
			continue
		}
		for i := uint32(0); i < n; i++ {
			g := _p_.runq[(h+i)%uint32(len(_p_.runq))]
			batch[(batchHead+i)%uint32(len(batch))] = g
		}
		if atomic.CasRel(&_p_.runqhead, h, h+n) { // cas释放，提交消耗
			return n
		}
	}
}

// 从p2 
// 的本地可运行队列中窃取一半元素，并放入p的本地可运行队列中。
// 返回其中一个被盗元素（如果失败，则返回零）。
func runqsteal(_p_, p2 *p, stealRunNextG bool) *g {
	t := _p_.runqtail
	n := runqgrab(p2, &_p_.runq, t, stealRunNextG)
	if n == 0 {
		return nil
	}
	n--
	gp := _p_.runq[(t+n)%uint32(len(_p_.runq))].ptr()
	if n == 0 {
		return gp
	}
	h := atomic.LoadAcq(&_p_.runqhead) // load acquire，与消费者同步
	if t-h+n >= uint32(len(_p_.runq)) {
		throw("runqsteal: runq overflow")
	}
	atomic.StoreRel(&_p_.runqtail, t+n) // store release，使商品可供消费
	return gp
}

// gQueue是通过g.schedlink链接的Gs的出列。一个G一次只能在一个G队列上或在一个G页面上。
type gQueue struct {
	head guintptr
	tail guintptr
}

// empty报告q是否为空。
func (q *gQueue) empty() bool {
	return q.head == 0
}

// push将gp添加到q的头部。
func (q *gQueue) push(gp *g) {
	gp.schedlink = q.head
	q.head.set(gp)
	if q.tail == 0 {
		q.tail.set(gp)
	}
}

// pushBack将gp添加到q的尾部。
func (q *gQueue) pushBack(gp *g) {
	gp.schedlink = 0
	if q.tail != 0 {
		q.tail.ptr().schedlink.set(gp)
	} else {
		q.head.set(gp)
	}
	q.tail.set(gp)
}

// pushBackAll将q2中的所有Gs添加到q的尾部。在该q2之后，必须禁止使用
// 。
func (q *gQueue) pushBackAll(q2 gQueue) {
	if q2.tail == 0 {
		return
	}
	q2.tail.ptr().schedlink = 0
	if q.tail != 0 {
		q.tail.ptr().schedlink = q2.head
	} else {
		q.head = q2.head
	}
	q.tail = q2.tail
}

// pop删除并返回队列q的头。如果
// q为空，则返回零。
func (q *gQueue) pop() *g {
	gp := q.head.ptr()
	if gp != nil {
		q.head = gp.schedlink
		if q.head == 0 {
			q.tail = 0
		}
	}
	return gp
}

// popList获取q中的所有g，并将它们作为gList返回。
func (q *gQueue) popList() gList {
	stack := gList{q.head}
	*q = gQueue{}
	return stack
}

// gList是通过g.schedlink链接的g列表。G一次只能在一个gQueue或gList上使用
// 。
type gList struct {
	head guintptr
}

// empty报告l是否为空。
func (l *gList) empty() bool {
	return l.head == 0
}

// push将gp添加到l的头部。
func (l *gList) push(gp *g) {
	gp.schedlink = l.head
	l.head.set(gp)
}

// pushAll将q中的所有G前置到l。
func (l *gList) pushAll(q gQueue) {
	if !q.empty() {
		q.tail.ptr().schedlink = l.head
		l.head = q.head
	}
}

// pop删除并返回l的头部。如果l为空，则返回零。
func (l *gList) pop() *g {
	gp := l.head.ptr()
	if gp != nil {
		l.head = gp.schedlink
	}
	return gp
}

// go:linkname setMaxThreads runtime/debug。setMaxThreads 
func setMaxThreads(in int) (out int) {
	lock(&sched.lock)
	out = int(sched.maxmcount)
	if in > 0x7fffffff { // MaxInt32 
		sched.maxmcount = 0x7fffffff
	} else {
		sched.maxmcount = int32(in)
	}
	checkmcount()
	unlock(&sched.lock)
	return
}

// go:nosplit 
func procPin() int {
	_g_ := getg()
	mp := _g_.m

	mp.locks++
	return int(mp.p.ptr().id)
}

// go:nosplit 
func procUnpin() {
	_g_ := getg()
	_g_.m.locks--
}

// go:linkname sync\u runtime\u procPin sync。运行时\u procPin 
// go:nosplit 
func sync_runtime_procPin() int {
	return procPin()
}

// go:linkname sync\u运行时\u procumpin sync。运行时\u procumpin 
// go:nosplit 
func sync_runtime_procUnpin() {
	procUnpin()
}

// go:linkname sync\u atomic\u运行时\u procPin sync/atomic。运行时\u procPin 
// go:nosplit 
func sync_atomic_runtime_procPin() int {
	return procPin()
}

// go:linkname sync\u atomic\u运行时\u procumpin sync/atomic。运行时_procumpinabcdefg
func sync_atomic_runtime_procUnpin() {
	procUnpin()
}

// 同步的活动旋转。互斥。
// go:linkname sync_runtime_canSpin sync。运行时_canspinabcdefg
func sync_runtime_canSpin(i int) bool {
	// sync。互斥是合作的，所以我们对旋转持保守态度。
	// 仅旋转几次，且仅在多核机器上运行，且
	// GOMAXPROCS>1，且至少有一个运行P且本地runq为空。
	// 与运行时互斥相反，我们这里不做被动旋转，
	// 因为可以在全局runq或其他Ps上工作。
	if i >= active_spin || ncpu <= 1 || gomaxprocs <= int32(sched.npidle+sched.nmspinning)+1 {
		return false
	}
	if p := getg().m.p.ptr(); !runqempty(p) {
		return false
	}
	return true
}

// go:linkname sync_runtime_doSpin sync。runtime_doSpin 
// go:nosplit 
func sync_runtime_doSpin() {
	procyield(active_spin_cnt)
}

var stealOrder randomOrder

// randoorder/randomEnum是随机工作窃取的助手类型。
// 它们允许以不同的伪随机顺序枚举所有P，而无需重复。
// 该算法基于这样一个事实：如果我们有X，那么X和GOMAXPROCS 
// 是互质的，那么（i+X）%GOMAXPROCS的序列给出了所需的枚举。
type randomOrder struct {
	count    uint32
	coprimes []uint32
}

type randomEnum struct {
	i     uint32
	count uint32
	pos   uint32
	inc   uint32
}

func (ord *randomOrder) reset(count uint32) {
	ord.count = count
	ord.coprimes = ord.coprimes[:0]
	for i := uint32(1); i <= count; i++ {
		if gcd(i, count) == 1 {
			ord.coprimes = append(ord.coprimes, i)
		}
	}
}

func (ord *randomOrder) start(i uint32) randomEnum {
	return randomEnum{
		count: ord.count,
		pos:   i % ord.count,
		inc:   ord.coprimes[i%uint32(len(ord.coprimes))],
	}
}

func (enum *randomEnum) done() bool {
	return enum.i == enum.count
}

func (enum *randomEnum) next() {
	enum.i++
	enum.pos = (enum.pos + enum.inc) % enum.count
}

func (enum *randomEnum) position() uint32 {
	return enum.pos
}

func gcd(a, b uint32) uint32 {
	for b != 0 {
		a, b = b, a%b
	}
	return a
}

// initTask表示需要对包进行的一组初始化。
// 与../保持同步/test/initempty。go:initTask 
type initTask struct {
	// TODO:将前3个字段打包得更紧？
	state uintptr // 0=未初始化，1=进行中，2=完成
	ndeps uintptr
	nfns  uintptr
	// 后面是一个*initTask的ndeps实例，每个包一个依赖于
	// 后面是nfns PC，每个init函数一个运行
}

// inittrace存储init函数的统计信息，当active为true时，这些函数由malloc和newproc更新。
var inittrace tracestat

type tracestat struct {
	active bool   // 初始化跟踪激活状态
	id     int64  // 初始化goroutine id 
	allocs uint64 // 堆分配
	bytes  uint64 // 堆分配字节
}

func doInit(t *initTask) {
	switch t.state {
	case 2: // 完全初始化
		return
	case 1: // 正在初始化
		throw("recursive call during initialization - linker skew")
	default: // 尚未初始化
		t.state = 1 // 正在初始化

		for i := uintptr(0); i < t.ndeps; i++ {
			p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize)
			t2 := *(**initTask)(p)
			doInit(t2)
		}

		if t.nfns == 0 {
			t.state = 2 // /初始化完成
			return
		}

		var (
			start  int64
			before tracestat
		)

		if inittrace.active {
			start = nanotime()
			// 非原子加载统计数据，因为tracinit仅由该init goroutine更新。
			before = inittrace
		}

		firstFunc := add(unsafe.Pointer(t), (3+t.ndeps)*goarch.PtrSize)
		for i := uintptr(0); i < t.nfns; i++ {
			p := add(firstFunc, i*goarch.PtrSize)
			f := *(*func())(unsafe.Pointer(&p))
			f()
		}

		if inittrace.active {
			end := nanotime()
			// 非原子加载统计信息，因为tracinit仅由该init goroutine更新。
			after := inittrace

			f := *(*func())(unsafe.Pointer(&firstFunc))
			pkg := funcpkgpath(findfunc(abi.FuncPCABIInternal(f)))

			var sbuf [24]byte
			print("init ", pkg, " @")
			print(string(fmtNSAsMS(sbuf[:], uint64(start-runtimeInitTime))), " ms, ")
			print(string(fmtNSAsMS(sbuf[:], uint64(end-start))), " ms clock, ")
			print(string(itoa(sbuf[:], after.bytes-before.bytes)), " bytes, ")
			print(string(itoa(sbuf[:], after.allocs-before.allocs)), " allocs")
			print("\n")
		}

		t.state = 2 // 初始化完成
	}
}
